Catégories
Tech

Le perceptron – Apprentissage supervisé dans Godot Engine

Le machine learning est un domaine passionnant. Je me suis intéressé récemment à comprendre le fonctionnement des réseaux de neurones. Aujourd’hui, je vous parle de leur brique élémentaire, j’ai nommé le perceptron. Ne vous inquiétez pas, ce nom peut paraître barbare, mais le concept reste relativement simple.

Un classificateur

Le perceptron, que l’on peut aussi appeler neurone, sert à classer des informations.

Nous pouvons le voir comme une sorte de boîte noire qui reçoit plusieurs valeurs en entrée. Ces informations sont traitées par le neurone qui nous sort un résultat.

Prenons un exemple concret. Sur un terrain, une clôture sépare deux enclos avec des chats et des chiens.

Notre perceptron a été entraîné pour nous indiquer dans quelle zone nous nous trouvons. Pour cela, nous lui passons des coordonnées [x, y] et le neurone nous retournera le bon enclos.

Fonctionnement

Comment est calculé le résultat ? C’est très simple, nous allons devoir déterminer l’influence de chaque entrée sur celui-ci. Pour cela, nous allons attribuer un poids à chacune d’elle.

Imaginez-vous ces poids comme des boutons de volume que nous allons pouvoir ajuster.

Pour chaque valeur que nous passons à notre perceptron, nous allons lui associer un poids. Si l’on reprend l’exemple de la classification ci-dessus, nous avons deux entrées : x et y. Nous aurons donc besoin de deux poids.

À l’initialisation de notre perceptron, nous allons créer autant de poids que d’entrée avec des valeurs aléatoires.

var weights: Array = []

func _init(n: int) -> void:
	randomize()
	for i in range(n):
		weights.push_back(rand_range(-1, 1))

Les poids vont nous permettre de déterminer la frontière entre les enclos. Pour cela, nous allons simplement additionner toutes les valeurs multipliées par leurs poids.

Si la somme totale est plus grande que zéro, notre position se trouve dans l’enclos des chats, sinon nous sommes dans la zone des chiens.

func compute(inputs: Array) -> bool:
	var sum: float = 0.0
	for i in range(weights.size()):
		sum += inputs[i] * weights[i]

	return sum > 0

Entraîner son perceptron

Avant de pouvoir nous donner un résultat correct, nous allons devoir entraîner notre perceptron.

L’entraînement consistera simplement à ajuster les poids en fonction des erreurs commises.

Pour cela, nous allons superviser et corriger les sorties du perceptron à l’aide de données de test dont nous savons le résultat. Je connais par exemple la position de mes chiens et chats.

func train(inputs: Array, target: int) -> int:
	var guess: int = 1 if compute(inputs) else -1
	var error: int = target - guess
	if error != 0:
		for i in range(weights.size()):
			weights[i] += error * inputs[i] * learning_rate

	return error

À chaque fois que le perceptron nous donne un mauvais résultat, tous les poids sont légèrement corrigés dans le sens inverse de l’erreur.

Voici une analogie pour vous expliquer plus simplement. Vous souhaitez placer un canapé dans votre salon et un ami vous aide en le poussant. Au fur et à mesure, vous lui donnez des indications pour décaler le meuble au bon endroit : un peu plus à droite, un peu à gauche, etc.

C’est exactement ce qui se passe dans la fonction d’entraînement. Petit à petit, tous les poids vont s’ajuster et les erreurs vont devenir de plus en plus minimes.

Plus votre jeu de données de test sera grand, plus votre résultat sera précis ! Sinon la marge d’erreur pourrait s’avérer trop importante.

Entraîné avec ces données de tests, ce résultat (en rouge) pourrait être considéré comme juste par le perceptron.

Code

J’ai réalisé un petit projet à l’aide de ce perceptron. Deux populations sont séparées par la ligne bleue. La ligne rouge représente le résultat trouvé. Nous pouvons observer ici la progression de l’apprentissage du neurone au fil du temps.

Retrouvez cet exemple directement sur ma page Github.

Voici le code au complet de la classe Perceptron en GDScript.

extends Node
class_name Perceptron

var learning_rate: float
var weights: Array = []

func _init(n: int, _learning_rate: float) -> void:
	randomize()
	learning_rate = _learning_rate
	for i in range(n):
		weights.push_back(rand_range(-1, 1))


func compute(inputs: Array) -> bool:
	var sum: float = 0.0
	for i in range(weights.size()):
		sum += inputs[i] * weights[i]

	return sum > 0

func train(inputs: Array, target: int) -> int:
	var guess: int = 1 if compute(inputs) else -1
	var error: int = target - guess
	if error != 0:
		for i in range(weights.size()):
			weights[i] += error * inputs[i] * learning_rate

	return error

La suite ?

Je ne vous ai pas parlé de la limite du perceptron… celui-ci n’est utile que pour des problèmes linéaires. Si dans mes exemples ma clôture avait été courbe, un seul neurone n’aurait pas pu trouver la solution.

Par la suite, je vais essayer d’assembler plusieurs de ces objets ensemble pour créer les fameux « réseaux de neurones ». Ce qui me permettra de résoudre des problèmes plus complexes.

C’est vrai qu’il existe des langages plus performants pour faire du machine learning. Mais si j’ai choisi de le développer dans Godot Engine, c’est pour pouvoir l’utiliser de manière ludique.

J’ai plusieurs idées pour mes prochains exemples.

  • Une voiture qui circule sur un circuit 2D
  • Un plateformer 2D à la Mario
  • Un snake
  • Etc.

Je crois qu’on a de quoi s’amuser ! En tout cas, j’ai hâte de pouvoir vous montrer mes prochaines créations.

Voilà, c’est tout pour aujourd’hui ! N’hésitez pas à me partager vos idées et à expérimenter avec le projet.