diff --git a/02_GraduallyDescentIntoTheSingularity/.gitattributes b/02_GraduallyDescentIntoTheSingularity/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/02_GraduallyDescentIntoTheSingularity/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/02_GraduallyDescentIntoTheSingularity/.gitignore b/02_GraduallyDescentIntoTheSingularity/.gitignore new file mode 100644 index 0000000..4709183 --- /dev/null +++ b/02_GraduallyDescentIntoTheSingularity/.gitignore @@ -0,0 +1,2 @@ +# Godot 4+ specific ignores +.godot/ diff --git a/02_GraduallyDescentIntoTheSingularity/2DScene.tscn b/02_GraduallyDescentIntoTheSingularity/2DScene.tscn new file mode 100644 index 0000000..ef4ea6a --- /dev/null +++ b/02_GraduallyDescentIntoTheSingularity/2DScene.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=5 format=3 uid="uid://bdred3heq7qc6"] + +[ext_resource type="Script" path="res://MLP.gd" id="1_m8mw3"] +[ext_resource type="Script" path="res://MLP_Visualization.gd" id="2_570bb"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_e4ydk"] +radius = 50.0 + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_pd6fs"] + +[node name="Node2D" type="Node2D"] +position = Vector2(534, 283) + +[node name="RigidBody2D" type="RigidBody2D" parent="."] + +[node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D"] +shape = SubResource("CircleShape2D_e4ydk") + +[node name="Sprite2D" type="Sprite2D" parent="RigidBody2D"] +scale = Vector2(70.25, 64.25) +texture = SubResource("PlaceholderTexture2D_pd6fs") + +[node name="MLP" type="Node" parent="."] +script = ExtResource("1_m8mw3") + +[node name="MLP_Visualization" type="Node2D" parent="." node_paths=PackedStringArray("mlp")] +script = ExtResource("2_570bb") +mlp = NodePath("../MLP") diff --git a/02_GraduallyDescentIntoTheSingularity/MLP.gd b/02_GraduallyDescentIntoTheSingularity/MLP.gd new file mode 100644 index 0000000..119ddf9 --- /dev/null +++ b/02_GraduallyDescentIntoTheSingularity/MLP.gd @@ -0,0 +1,112 @@ +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 diff --git a/02_GraduallyDescentIntoTheSingularity/MLP_Visualization.gd b/02_GraduallyDescentIntoTheSingularity/MLP_Visualization.gd new file mode 100644 index 0000000..af5f083 --- /dev/null +++ b/02_GraduallyDescentIntoTheSingularity/MLP_Visualization.gd @@ -0,0 +1,30 @@ +extends Node2D + +@export var mlp: MLP +@export var neuron_radius: float = 10.0 +@export var space_between_layers: float = 100.0 +@export var space_between_neurons: float = 50.0 +@export var neuron_color = Color(1, 1, 1) # white +@export var connection_color = Color(0, 0, 1) # blue + +func _draw(): + if mlp: + var layer_position = Vector2(0, 0) + for i in range(mlp.weights.size()): + var neuron_position = Vector2(layer_position) + for j in range(mlp.weights[i].size()): + var neuron = mlp.weights[i][j] + # draw neuron + draw_circle(neuron_position, neuron_radius, neuron_color) + # draw connections to the next layer + if i < mlp.weights.size() - 1: + var next_layer_position = Vector2(layer_position.x + space_between_layers, 0) + for k in range(mlp.weights[i + 1].size()): + var next_neuron_position = Vector2(next_layer_position) + draw_line(neuron_position, next_neuron_position, connection_color) + next_layer_position.y += space_between_neurons + neuron_position.y += space_between_neurons + layer_position.x += space_between_layers + +func update_mlp(new_mlp: MLP): + mlp = new_mlp diff --git a/02_GraduallyDescentIntoTheSingularity/icon.svg b/02_GraduallyDescentIntoTheSingularity/icon.svg new file mode 100644 index 0000000..adc26df --- /dev/null +++ b/02_GraduallyDescentIntoTheSingularity/icon.svg @@ -0,0 +1 @@ + diff --git a/02_GraduallyDescentIntoTheSingularity/icon.svg.import b/02_GraduallyDescentIntoTheSingularity/icon.svg.import new file mode 100644 index 0000000..0db3dfe --- /dev/null +++ b/02_GraduallyDescentIntoTheSingularity/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dw0t8inj75cb5" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/02_GraduallyDescentIntoTheSingularity/project.godot b/02_GraduallyDescentIntoTheSingularity/project.godot new file mode 100644 index 0000000..b36a3dc --- /dev/null +++ b/02_GraduallyDescentIntoTheSingularity/project.godot @@ -0,0 +1,20 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Gradually Descent Into The Singularity" +run/main_scene="res://2DScene.tscn" +config/features=PackedStringArray("4.0", "Forward Plus") +config/icon="res://icon.svg" + +[physics] + +2d/default_gravity=0.0