113 lines
3.1 KiB
GDScript
113 lines
3.1 KiB
GDScript
extends Node
|
|
class_name MLP
|
|
|
|
|
|
@export var weights: Array[Array] = []
|
|
@export var biases: Array[Array] = []
|
|
@export var learning_rate: float = 0.01
|
|
|
|
func _init(sizes: Array = [3,2]) -> void:
|
|
randomize()
|
|
weights = []
|
|
biases = []
|
|
for i in range(sizes.size() - 1):
|
|
weights.append([])
|
|
biases.append([])
|
|
for _j in range(sizes[i]):
|
|
weights[i].append([])
|
|
for _k in range(sizes[i + 1]):
|
|
weights[i][_j].append(randf() * 2 - 1)
|
|
biases[i].append(randf() * 2 - 1)
|
|
|
|
# Feedforward: compute the output of the MLP for a given input.
|
|
func feedforward(input: Array) -> Array:
|
|
var a = input
|
|
for i in range(weights.size()):
|
|
var dp = dot_product(a, weights[i])
|
|
for j in range(a.size()):
|
|
a[j] = sigmoid(dp[j] + + biases[i][j])
|
|
return a
|
|
|
|
# Backpropagation: update the weights and biases based on the input and target output.
|
|
func backpropagate(input: Array, target: Array) -> void:
|
|
var nabla_b: Array = []
|
|
var nabla_w: Array = []
|
|
|
|
# Feedforward
|
|
var activation:Array[float] = input
|
|
var activations: Array[Array] = [input] # List to store all the activations, layer by layer
|
|
var zs: Array[Array] = [] # List to store all the z vectors, layer by layer
|
|
|
|
var z: int = 0
|
|
var sp: float = 0.0
|
|
var delta: Array = []
|
|
|
|
|
|
for i in range(weights.size()):
|
|
var dp = dot_product(activation, weights[i])
|
|
var zs_i:Array[float] = []
|
|
var activations_i: Array[float] = []
|
|
for j in range(dp.size()):
|
|
|
|
z = dp[j] + biases[i][j]
|
|
zs_i.append(z)
|
|
activations_i.append(sigmoid(z))
|
|
|
|
activation = activations_i
|
|
|
|
zs.append(zs_i)
|
|
activations.append(activations_i)
|
|
|
|
# Backward pass
|
|
sp = sigmoid_prime(zs[zs.size() - 1])
|
|
|
|
for cd in cost_derivative(activations[activations.size() - 1], target):
|
|
delta.append(cd * sp)
|
|
nabla_b.append(delta)
|
|
nabla_w.append(dot_product(delta, activations[activations.size() - 2].transpose()))
|
|
|
|
for l in range(2, weights.size() + 1):
|
|
z = zs[zs.size() - l]
|
|
sp = sigmoid_prime(z)
|
|
delta = []
|
|
for dp in dot_product(weights[weights.size() - l + 1].transpose(), delta):
|
|
delta.append(dp * sp)
|
|
nabla_b.append(delta)
|
|
nabla_w.append(dot_product(delta, activations[activations.size() - l - 1].transpose()))
|
|
|
|
# Update weights and biases
|
|
for i in range(weights.size()):
|
|
weights[i] -= learning_rate * nabla_w[nabla_w.size() - i - 1]
|
|
biases[i] -= learning_rate * nabla_b[nabla_b.size() - i - 1]
|
|
|
|
func sigmoid(x: float) -> float:
|
|
return 1.0 / (1.0 + exp(-x))
|
|
|
|
func sigmoid_prime(x: float) -> float:
|
|
return sigmoid(x) * (1 - sigmoid(x))
|
|
|
|
func cost_derivative(output_activations: Array, y: Array) -> Array:
|
|
var output: Array = []
|
|
for i in range(0, output_activations.size()):
|
|
output[i] = output_activations[i] - y[i]
|
|
return output
|
|
|
|
func dot_product(a: Array, b: Array) -> Array:
|
|
var result: Array = []
|
|
for i in range(a.size()):
|
|
var sum: float = 0.0
|
|
for j in range(a[i].size()):
|
|
sum += a[i][j] * b[j]
|
|
result.append(sum)
|
|
return result
|
|
|
|
|
|
# Called when the node enters the scene tree for the first time.
|
|
func _ready():
|
|
pass # Replace with function body.
|
|
|
|
|
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
|
func _process(delta):
|
|
pass
|