Skip to content

Intelligence artificielle et bêtise naturelle

Je viens de finir mon tour d’horizon de l’intelligence artificielle en suivant les cours de Franck Yu https://cs50.harvard.edu/ai/2023/ sur le sujet. J’en ai retenu au moins deux choses:

  • Je n’aime pas ça, l’idée qu’un ordinateur puisse faire des choix en fonction de critères qui échappent au commun des mortels est une idée qui me déplait fortement. Principalement parce que cela donne un pouvoir démesuré à une caste de technocrates qui sont à l’origine des critères de décision.
  • La seconde dépend directement de la première: l’intelligence artificielle n’existe pas. J’entends par là que le terme d’intelligence artificielle est trompeur, car il n’y a aucune intelligence derrière, mais juste une suite d’algorithmes et de paramètres que seule cette caste de technocrates comprend.

Lorsqu’un ordinateur joue aux échecs, il se contente de profiter de son énorme puissance de calcul pour estimer tous les coups possibles aussi loin que sa puissance le lui permet, d’en estimer une valeur en fonction de critères connus (nombre et placement des pièces), puis de choisir le coup qui compte le plus de points. Il n’y aucune stratégie, aucun instinct, aucune intelligence derrière les coups d’un ordinateur, juste des calculs dans une proportion qu’aucun cerveau humain ne peut égaler.

Mais celui qui apprend, me direz-vous. L’ordinateur se contente d’enregistrer les mouvements qu’il a joué et de leur attribuer une note selon qu’il ai gagné ou perdu la partie. Il n’a plus qu’à regarder dans cette base de données la valeur des mouvements avant de jouer.

L’humain lui n’a qu’une puissance de calcul dérisoire et une mémoire de poisson rouge à côté. Il joue avec des stratégies, de l’expérience, de l’instinct. Et pourtant, malgré l’inégalité des forces, il gagne encore contre la machine. C’est cela la vraie intelligence.

Je vous vois arriver avec les réseaux de neurones. Ils apprennent à lire, à analyser une image, même une vidéo. C’est vrai, mais là aussi, il n’y a aucune intelligence. Chaque neurone dispose d’un algorithme ou il multiplie des valeurs par des poids, puis rajoute un biais qu’il a estimé en fonction d’un algorithme tout aussi simple qu’il a utilisé lorsqu’on lui a donné des valeurs de référence.

Encore une fois, cela fonctionne grace `a la puissance de calcul incroyablement élevée d’un ordinateur et malgré cela, l’ordinateur fera toujours une analyse moins bonne qu’un être humain. Il se trompera en lisant des chiffres, verra quelque chose qui n’existe pas dans une photographie. Parce qu’il n’est pas intelligent, il se contente de comparer statistiquement des données en fonction d’autres données.

Voitures et pièces détachées

Premières photos avec le Artra Lab OCULILUMEN 7.5mm F2.8 et je confirme mes premières impressions:

  • L’objectif tout en métal est beaucoup trop lourd pour le Nikon Zfc
  • Le manque de crans sur la bague de diaphragme oblige à vérifier l’ouverture régulièrement.
  • Les coins sont carrément flous et très sombres. C’est normal sur un fisheye, mais là on est sérieusement dans le cul de bouteille.

Par contre, le centre est excellent et le manque d’AF, vu la profondeur de champs gigantesque, ne dérange pas du tout.

Artra Lab OCULILUMEN 7.5mm F2.8

J’ai acquis cet objectif directement depuis le site d’Artra Lab. Artra Lab est une société basée à Hong Kong, ils ont visiblement des bureaux à Shenzhen.

Le site du constructeur est plutôt élégant et bien, la procédure en ligne se fait sans problème, paiement par Paypal. L’objectif est envoyé par transporteur UPS ou DHL dans un délai d’une semaine, il est correctement emballé et livré avec une petite housse du plus bel effet.

Premier mauvais point, dans sa déclaration aux douanes, Artra Lab a déclaré un objectif d’une valeur de 500HKD, c’est à dire moins de la moitié de la valeur de l’objectif. C’est ennuyeux parce que les douanes ont fait leur travail, ils ont vérifié et que cela m’a couté des frais et un délai de livraison supplémentaire d’ne semaine. Bref, ce n’est pas très sérieux. J’ai prévenu Artra Lab du problème, mais ils n’ont répondu à aucun email.

L’objectif fait sérieux, il est tout en métal et relativement lourd pour mon Nikon Zfc, trop lourd. La bague de diaphragme n’est pas crantée et elle bouge au moindre frottement. La bague de mise au point n’est pas mieux et aucun mécanisme pour la bloquer (ce qui serait utile vu l’immense profondeur de champs de l’objectif).

Les distortions sont moins marquées que ce à quoi on peut s’attendre d’une telle focale. Les bords sont flous. C’est un cul de bouteille, mais c’est un peu la définition du fish eye, donc rien à lui reprocher de ce côté.

Je n’achèterais pas d’autre objectifs de la marque, le manque de crans sur la bague de diaphragme est éliminatoire en ce qui me concerne. Un morceau de scotch règlera l’affaire, ce n’est pas sérieux.

Un neurone en Common Lisp

Un neurone est une fonction extrêmement simple qui prend un certain nombre de paramètres et retourne soit 0 (ou -1) soit 1 selon la valeur des arguments qui lui sont passés. Il multiplie chacun de ces argument par une valeur, additionne le tout et rajoute une constante. Si le résultat de cette opération est positive, alors il retourne 1, si elle est négative, il retourne -1.

L’intérêt de ce neurone, c’est qu’il est capable d’apprendre, c’est à dire que si on lui montre un jeu de données avec le résultat attendu, il va régler de lui même les constantes à utiliser pour sa petite opération et essayer de trouver une valeur idéale pour retourner la bonne réponse.

Ça vous intéresse, je vous suggère la lecture de la page Wikipedia et de faire vos propres recherches : https://en.wikipedia.org/wiki/Perceptron

Mon implémentation est sans doute très naïve et je suis certain qu’il existe une tonne de librairies qui le font beaucoup mieux que moi. Mais l’objectif ici est de comprendre le rouage de la chose, comment cela fonctionne. Une fois cela compris, il est certain que se tourner vers une librairie, complète, testée et optimisée est tout à fait recommandable pour faire un vrai projet en prod.



(setf data '(
	     (20 2000 1)
	     (40 1000 -1)
	     (30 3000 1)
	     (30 1000 -1)
	     (20 3000 1)
	     (40 2000 -1)
	     (40 4000 1)
	     (20 2000 -1)
	     (30 4000 1)
	     (20 4000 1)
	     (40 3000 -1)))


(defvar w '(1 1 1))

(defun magic (w x y)
  (if (> (+ (first w) (* (second w) x) (* (third w) y)) 0)
      1
      -1))


(dolist (l data)
  (format t "-----~%")
  (format t "~A~%" l)
  (let ((c (magic w (first l) (second l)))
	(alpha 0.00001))
    (format t "G/S=> ~A/~A~%" c (third l))
    
    (setf (first w)
	  (+ (first w) (*
			1
			alpha
			(- (third l) c))))
    (setf (second w)
	  (+ (second w) (*
			 (first l)
			 alpha
			 (- (third l) c ))))
    (setf (third w)
	  (+ (third w) (*
			(second l)
			alpha
			(- (third l) c))))
    (format t "~A~%" w)))
  
  
	     
      

Jouons au Tic Tac Toc

Je n’ai pas la moindre idée de comment se nomme ce jeu en Français, il s’agit de cette grille de trois sur trois ou l’on rentre des X et des O. Le premier à avoir aligné trois caractères identique a gagné la partie.

Dans ma course à la compréhension de l’Intelligence artificielle, j’ai donc décidé de m’attaquer `a ce jeu et d’implanter l’algorithme Minimax en Common Lisp.

L’ordinateur n’est pas imbattable, en fait, il suffit d’un peu de stratégie pour gagner, mais il tient quand même la partie. Je pense qu’il faudrait pondérer les coups en fonction du nombre de coups avant de gagner pour améliorer le système. Mais qu’à cela ne tienne, l’ordinateur connait les règles, il essaye de gagner et essaye de faire perdre son adversaire.

Le principle consiste tout simplement à visualiser par avance toutes les parties possibles pour chaque coup et de jouer en priorité ceux qui lui permettent d’arriver à la victoire.

Le code ci-dessous:

#!/opt/homebrew/bin/sbcl --script


(defmacro t-equal (p a b c)
  `(and
    (equal ,p ,a)
    (equal ,a ,b)
    (equal ,a ,c)))

(defun arrived (player state)
  (or
   (t-equal player (nth 0 state) (nth 1 state) (nth 2 state))
   (t-equal player (nth 3 state) (nth 4 state) (nth 5 state))
   (t-equal player (nth 6 state) (nth 7 state) (nth 8 state))
   (t-equal player (nth 0 state) (nth 4 state) (nth 8 state))
   (t-equal player (nth 2 state) (nth 4 state) (nth 6 state))
   (t-equal player (nth 0 state) (nth 3 state) (nth 6 state))
   (t-equal player (nth 1 state) (nth 4 state) (nth 7 state))
   (t-equal player (nth 2 state) (nth 5 state) (nth 8 state))))

(defun actions (state player)
  (let ((ret ()))
    (dotimes (i 9)
      (when (null (nth i state))
	(let ((node (copy-list state)))
	  (setf (nth i node) player)
	  (push node ret))))
    ret))

(defun display-board (state)
  (format t "--~%~%~A|~A|~A~%-----~%~A|~A|~A~%-----~%~A|~A|~A~%"
	  (or (nth 0 state) " ")
	  (or (nth 1 state) " ")
	  (or (nth 2 state) " ")
	  (or (nth 3 state) " ")
	  (or (nth 4 state) " ")
	  (or (nth 5 state) " ")
	  (or (nth 6 state) " ")
	  (or (nth 7 state) " ")
	  (or (nth 8 state) " ")))

(defun other-player (player)
  (if (equal player 1)
      2
      1))

(defun utility (state)
  (when (arrived 1 state)
    (return-from utility -1))
  (when (arrived 2 state)
    (return-from utility 1))
  0)

(defun arrived? (state)
  (or
   (arrived 1 state)
   (arrived 2 state)))

(defun max-value (state)
  (when (arrived? state)
    (return-from max-value (utility state)))
  (let ((actions (actions state 2))
	(l '(-99999999999)))
    (dolist (action actions)
      (push (min-value action) l))
    (reduce #'max l)))

(defun min-value (state)
  (when (arrived? state)
    (return-from min-value (utility state)))
  (let ((actions (actions state 1))
	(l '(9999999999)))
    (dolist (action actions)
      (push (max-value action) l))
    (reduce #'min l)))



(defun ask-user (state)
  (display-board state)
  (format t "From 0 to 8, where do you play ?~%")
  (let ((input (read)))
    (setf (nth input state) 1))
  state)

(defvar state '(nil nil nil nil nil nil nil nil nil))

(loop
  (when (arrived? state)
    (display-board state)
    (format t "Computer wins~%")
    (return))
  (setf state (ask-user state))
  (when (arrived? state)
    (display-board state)
    (format t "User wins~%")
    (return))
  
  (let ((actions (actions state 2))
	(temporary-state nil)
	(v -9999))
    (dolist (action actions)
      (let ((w (min-value action)))
	(when (> w v)
	  (display-board action)
	  (format t "Got ~A~%" w)
	  (setf v w)
	  (setf temporary-state action))))
    (setf state temporary-state)))


    

     
  

Prière de rue

Ce n’est pas très loin de mon quartier que s’est passé cette scène. Il est fréquent de voir des gens prier auprès d’un prêtre. Mais cette scène ou un groupe entier prie dans la rue ainsi, je ne l’avais jamais vu avant, je ne l’ai jamais revu après.

Le plus court chemin

Me voici lancé dans les cours d’Intelligence Artificielles de Harvard. Le court entier de Brian Yu sont disponibles sur le lien suivant: https://cs50.harvard.edu/ai/2023/

Le cour porte à la fois sur les algorithmes utilisés en I.A. et sur leurs implantations en Python. Comme je n’aime ni Python ni les choses simples, j’ai donc décidé de suivre les cours et de reproduire autant que faire se peut les algorithmes en Common Lisp (un language bien plus élégant que Python).

Mon premier algorithme est celui de la recherche de solution. Nous avons une liste avec une série de chiffres, tels que 3756421, c’est l’état initial. Et une autre série de chiffres, tels que 1234567, qui représentent l’objectif à atteindre. À chaque coup, il faut bouger échanger deux chiffres, juste deux chiffres.

Attention, ce n’est pas un algorithme de tri, l’objectif n’est pas de trier les chiffres dans le bon ordre. L’objectif est d’implanter un algorithme de résolution d’un problème par étapes en suivant une règle précise.

Ensuite, nous avons une frontière, c’est la liste des solutions possibles en partant d’un état à l’état suivant. Cette liste est rentrée dans une queue. Puis nous avons un agent qui va vérifier pour chaque entrée de cette queue si elle correspond à l’objectif recherché et si non, chercher tous les états frontières qui n’ont pas déjà été vérifiés et les rajouter dans la queue.

Il s’agit de l’algorithme Breadth-First Search. Malheureusement, il n’est jamais plus fort que l’être humain, mais c’est tout à fait normal, puisqu’il ne connait pas l’objectif à l’avance. Il avance à un peu au hasard et retourne la première solution qu’il a trouvé, même si cette dernière n’est pas forcément la meilleure. On peut améliorer cela si on connait l’objectif en comparant l’état de chaque chiffre avec ceux de l’objectif et donc ne bouger que ceux qui nous en rapprochent. Cela porte un nom, c’est la Greedy Best-First Search.

Mon code source:

#!/opt/homebrew/bin/sbcl --script


(defclass node ()
  ((value :initarg :value
	  :accessor node-value)
   (parent :initform nil
	   :initarg :parent
	   :accessor node-parent)))

(defmethod node-name ((obj node))
  (format nil "~A" (node-value obj)))

(defmethod node-parent-name ((obj node))
  (format nil "~A" (node-parent obj)))

(let ((queue ()))
  (defun add-queue (value)
    (push value queue))
  (defun pull-queue ()
    (let ((ret (first (last queue))))
      (setf queue (reverse (cdr (reverse queue))))
      ret)))

(defvar initial-state (make-instance 'node :value (list 3 1 2 7 5 6 4)))
(defvar target-state (make-instance 'node :value (list 1 2 3 4 5 6 7)))

(defun arrived? (state)
  (equal (node-value state) (node-value target-state)))

(defun exchange (s x y)
  (let ((copy-s (copy-list s)))
    (setf (nth x copy-s) (nth y s))
    (setf (nth y copy-s) (nth x s))
    copy-s))
    
(let ((done-state (make-hash-table :test 'equal))
      (frontiers ()))
  (setf (gethash (node-name initial-state) done-state) initial-state)
  (defun frontiers (state)
    (dotimes (i (- (length state) 1))
      (let* ((proposed-state (exchange state 0 (+ 1 i)))
	     (node (make-instance 'node :value proposed-state :parent state)))
	(when (not (gethash (node-name node) done-state))
	  (setf (gethash (node-name node) done-state) node)
	  (add-queue node)))))
  
  (defun find-path (state li)
    (push (node-name state) li)
    (when (null (node-parent state))
      (return-from find-path li))
    (find-path (gethash (node-parent-name state) done-state) li)))


(defun action (state)
  (frontiers (node-value state))
  (loop
    (let ((ff (pull-queue)))
      (when (null ff)
	(format t "No solution~%"))
      (when (arrived? ff)
	(return-from action ff))
      (frontiers (node-value ff)))))


(let ((ff (action initial-state)))
  (format t "From ~A to ~A~%" (node-name initial-state) (node-name ff))
  (format t "~A~%" (find-path ff () )))