<jupyter_start><jupyter_text>Perceptron Multicouche Auteur: <NAME> [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jdhp-docs/notebooks/master?urlpath=apps%2Fnb_sci_ai%2Fai_ml_multilayer_perceptron_fr.ipynb)<jupyter_code>%matplotlib inline

#import nnfigs

# https://github.com/jeremiedecock/neural-network-figures.git
import nnfigs.core as nnfig
import matplotlib.pyplot as plt
import numpy as np

import ipywidgets
from ipywidgets import interact

# TODO

# Shallow et Deep learning à lire:
# - https://www.miximum.fr/blog/introduction-au-deep-learning-2/
# - https://sciencetonnante.wordpress.com/2016/04/08/le-deep-learning/
# - https://www.technologies-ebusiness.com/enjeux-et-tendances/le-deep-learning-pas-a-pas
# - http://scholar.google.fr/scholar_url?url=https://arxiv.org/pdf/1404.7828&hl=fr&sa=X&scisig=AAGBfm07Y2UDlPpbninerh4gxHUj2SJfDQ&nossl=1&oi=scholarr&sqi=2&ved=0ahUKEwjfxMu7jKnUAhUoCsAKHR_RDlkQgAMIKygAMAA<jupyter_output><empty_output><jupyter_text>$\newcommand{\cur}{i}\newcommand{\prev}{j}\newcommand{\prevcur}{{\cur\prev}}\newcommand{\next}{k}\newcommand{\curnext}{{\next\cur}}\newcommand{\ex}{\eta}\newcommand{\pot}{\rho}\newcommand{\feature}{x}\newcommand{\weight}{w}\newcommand{\wcur}{{\weight_{\cur\prev}}}\newcommand{\activthres}{\theta}\newcommand{\activfunc}{f}\newcommand{\errfunc}{E}\newcommand{\learnrate}{\epsilon}\newcommand{\learnit}{n}\newcommand{\sigout}{y}\newcommand{\sigoutdes}{d}\newcommand{\weights}{\boldsymbol{W}}\newcommand{\errsig}{\Delta}$<jupyter_code># Notations :
# - $\cur$: couche courante
# - $\prev$: couche immédiatement en amont de la courche courrante (i.e. vers la couche d'entrée du réseau)
# - $\next$: couche immédiatement en aval de la courche courrante (i.e. vers la couche de sortie du réseau)
# - $\ex$: exemple (*sample* ou *feature*) courant (i.e. le vecteur des entrées courantes du réseau)
# - $\pot_\cur$: *Potentiel d'activation* du neurone $i$ pour l'exemple courant
# - $\wcur$: Poids de la connexion entre le neurone $j$ et le neurone $i$
# - $\activthres_\cur$: *Seuil d'activation* du neurone $i$
# - $\activfunc_\cur$: *Fonction d'activation* du neurone $i$
# - $\errfunc$: *Fonction objectif* ou *fonction d'erreur*
# - $\learnrate$: *Pas d'apprentissage* ou *Taux d'apprentissage*
# - $\learnit$: Numéro d'itération (ou cycle ou époque) du processus d'apprentissage
# - $\sigout_\cur$: Signal de sortie du neurone $i$ pour l'exemple courant
# - $\sigoutdes_\cur$: Sortie désirée (*étiquette*) du neurone $i$ pour l'exemple courant
# - $\weights$: Matrice des poids du réseau (en réalité il y a une matrice de taille potentiellement différente par couche)
# - $\errsig_i$: *Signal d'erreur* du neurone $i$ pour l'exemple courant<jupyter_output><empty_output><jupyter_text>Introduction  Qu'est-ce qu'un réseau de neurones ?Une grosse fonction parametrique.Pour peu qu'on donne suffisamment de paramètres à cette fonction, elle est capable d'approximer n'importe quelle fonction continue.Représentation schématique d'une fonction paramètrique avec 3 paramètres avec une entrée en une sortie à 1 dimension$$\mathbb{R} \rightarrow \mathbb{R}$$$$x \mapsto g_{\boldsymbol{\omega}}(x)$$TODO: image/schéma intuition : entrés -> fonction avec paramètres = table de mixage -> sortie  À quoi ça sert ?TODO : expliquer la régression et la classificationTODO : applications avec références...Exemples d'application concrètes :- Reconnaissance de texte manuscrit- Reconnaissance de formes, d'objets, de visages, etc. dans des images- Reconnaissance de la parole- Prédiction de séries temporelles (cours de la bourse, etc.)- etc.  Définition du neurone "formel"<jupyter_code>STR_CUR = r"i"       # Couche courante
STR_PREV = r"j"      # Couche immédiatement en amont de la courche courrante (i.e. vers la couche d'entrée du réseau)
STR_NEXT = r"k"      # Couche immédiatement en aval de la courche courrante (i.e. vers la couche de sortie du réseau)
STR_EX = r"\eta"     # Exemple (*sample* ou *feature*) courant (i.e. le vecteur des entrées courantes du réseau)
STR_POT = r"x"       # *Potentiel d'activation* du neurone $i$ pour l'exemple $\ex$
STR_POT_CUR = r"x_i"       # *Potentiel d'activation* du neurone $i$ pour l'exemple $\ex$
STR_WEIGHT = r"w"
STR_WEIGHT_CUR = r"w_{ij}"  # Poids de la connexion entre le neurone $j$ et le neurone $i$
STR_ACTIVTHRES = r"\theta"  # *Seuil d'activation* du neurone $i$
STR_ACTIVFUNC = r"f"        # *Fonction d'activation* du neurone $i$
STR_ERRFUNC = r"E"          # *Fonction objectif* ou *fonction d'erreur*
STR_LEARNRATE = r"\epsilon" # *Pas d'apprentissage* ou *Taux d'apprentissage*
STR_LEARNIT = r"n"          # Numéro d'itération (ou cycle ou époque) du processus d'apprentissage
STR_SIGIN = r"x"            # Signal de sortie du neurone $i$ pour l'exemple $\ex$
STR_SIGOUT = r"y"           # Signal de sortie du neurone $i$ pour l'exemple $\ex$
STR_SIGOUT_CUR = r"y_i"
STR_SIGOUT_PREV = r"y_j"
STR_SIGOUT_DES = r"d"           # Sortie désirée (*étiquette*) du neurone $i$ pour l'exemple $\ex$
STR_SIGOUT_DES_CUR = r"d_i"
STR_WEIGHTS = r"W"              # Matrice des poids du réseau (en réalité il y a une matrice de taille potentiellement différente par couche)
STR_ERRSIG = r"\Delta"          # *Signal d'erreur* du neurone $i$ pour l'exemple $\ex$

def tex(tex_str):
    return r"$" + tex_str + r"$"
fig, ax = nnfig.init_figure(size_x=8, size_y=4)

nnfig.draw_synapse(ax, (0, -6), (10, 0))
nnfig.draw_synapse(ax, (0, -2), (10, 0))
nnfig.draw_synapse(ax, (0, 2),  (10, 0))
nnfig.draw_synapse(ax, (0, 6),  (10, 0), label=tex(STR_WEIGHT_CUR), label_position=0.5, fontsize=14)

nnfig.draw_synapse(ax, (10, 0), (12, 0))

nnfig.draw_neuron(ax, (0, -6), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, -2), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, 2),  0.5, empty=True)
nnfig.draw_neuron(ax, (0, 6),  0.5, empty=True)
plt.text(x=0,    y=7.5,  s=tex(STR_PREV),        fontsize=14)
plt.text(x=10,   y=1.5,  s=tex(STR_CUR),         fontsize=14)
plt.text(x=0,    y=0,    s=r"$\vdots$",          fontsize=14)
plt.text(x=-2.5, y=0,    s=tex(STR_SIGOUT_PREV), fontsize=14)
plt.text(x=13,   y=0,    s=tex(STR_SIGOUT_CUR),  fontsize=14)
plt.text(x=9.2,  y=-1.8, s=tex(STR_POT_CUR),     fontsize=14)

nnfig.draw_neuron(ax, (10, 0), 1, ag_func="sum", tr_func="sigmoid")

plt.show()<jupyter_output><empty_output><jupyter_text>$$\sigout = \activfunc \left( \sum_i \weight_i \feature_i \right)$$ $$\pot_\cur = \sum_\prev \wcur \sigout_{\prev}$$$$\sigout_{\cur} = \activfunc(\pot_\cur)$$$$\weights = \begin{pmatrix}    \weight_{11} & \cdots & \weight_{1m} \\    \vdots       & \ddots & \vdots       \\    \weight_{n1} & \cdots & \weight_{nm}\end{pmatrix}$$ Avec :- $\cur$: couche courante- $\prev$: couche immédiatement en amont de la courche courrante (i.e. vers la couche d'entrée du réseau)- $\next$: couche immédiatement en aval de la courche courrante (i.e. vers la couche de sortie du réseau)- $\ex$: exemple (*sample* ou *feature*) courant (i.e. le vecteur des entrées courantes du réseau)- $\pot_\cur$: *Potentiel d'activation* du neurone $i$ pour l'exemple courant- $\wcur$: Poids de la connexion entre le neurone $j$ et le neurone $i$- $\activthres_\cur$: *Seuil d'activation* du neurone $i$- $\activfunc_\cur$: *Fonction d'activation* du neurone $i$- $\errfunc$: *Fonction objectif* ou *fonction d'erreur*- $\learnrate$: *Pas d'apprentissage* ou *Taux d'apprentissage*- $\learnit$: Numéro d'itération (ou cycle ou époque) du processus d'apprentissage- $\sigout_\cur$: Signal de sortie du neurone $i$ pour l'exemple courant- $\sigoutdes_\cur$: Sortie désirée (*étiquette*) du neurone $i$ pour l'exemple courant- $\weights$: Matrice des poids du réseau (en réalité il y a une matrice de taille potentiellement différente par couche)- $\errsig_i$: *Signal d'erreur* du neurone $i$ pour l'exemple courant  Fonction d'activation  Fonction sigmoïdeLa fonction sigmoïde (en forme de "S") est définie par :$$f(x) = \frac{1}{1 + e^{-x}}$$pour tout réel $x$.On peut la généraliser à toute fonction dont l'expression est :$$f(x) = \frac{1}{1 + e^{-\lambda x}}$$<jupyter_code>def sigmoid(x, _lambda=1.):
    y = 1. / (1. + np.exp(-_lambda * x))
    return y
%matplotlib inline

x = np.linspace(-5, 5, 300)

y1 = sigmoid(x, 1.)
y2 = sigmoid(x, 5.)
y3 = sigmoid(x, 0.5)

plt.plot(x, y1, label=r"$\lambda=1$")
plt.plot(x, y2, label=r"$\lambda=5$")
plt.plot(x, y3, label=r"$\lambda=0.5$")

plt.hlines(y=0, xmin=-5, xmax=5, color='gray', linestyles='dotted')
plt.vlines(x=0, ymin=-2, ymax=2, color='gray', linestyles='dotted')

plt.legend()

plt.title("Fonction sigmoïde")
plt.axis([-5, 5, -0.5, 2]);<jupyter_output><empty_output><jupyter_text>Tangente hyperbolique<jupyter_code>def tanh(x):
    y = np.tanh(x)
    return y
x = np.linspace(-5, 5, 300)
y = tanh(x)

plt.plot(x, y)

plt.hlines(y=0, xmin=-5, xmax=5, color='gray', linestyles='dotted')
plt.vlines(x=0, ymin=-2, ymax=2, color='gray', linestyles='dotted')

plt.title("Fonction tangente hyperbolique")
plt.axis([-5, 5, -2, 2]);<jupyter_output><empty_output><jupyter_text>Fonction logistique Fonctions ayant pour expression$$f(t) = K \frac{1}{1+ae^{-\lambda t}}$$où $K$ et $\lambda$ sont des réels positifs et $a$ un réel quelconque.Les fonctions sigmoïdes sont un cas particulier de fonctions logistique avec $a > 0$.<jupyter_code>def logistique(x, a=1., k=1., _lambda=1.):
    y = k / (1. + a * np.exp(-_lambda * x))
    return y
%matplotlib inline

x = np.linspace(-5, 5, 300)

y1 = logistique(x, a=1.)
y2 = logistique(x, a=2.)
y3 = logistique(x, a=0.5)

plt.plot(x, y1, label=r"$a=1$")
plt.plot(x, y2, label=r"$a=2$")
plt.plot(x, y3, label=r"$a=0.5$")

plt.hlines(y=0, xmin=-5, xmax=5, color='gray', linestyles='dotted')
plt.vlines(x=0, ymin=-2, ymax=2, color='gray', linestyles='dotted')

plt.legend()

plt.title("Fonction logistique")
plt.axis([-5, 5, -0.5, 2]);<jupyter_output><empty_output><jupyter_text>Le terme de biaisTODO<jupyter_code>fig, ax = nnfig.init_figure(size_x=8, size_y=6)

HSPACE = 6
VSPACE = 4

# Synapse #####################################

#nnfig.draw_synapse(ax, (0,2*VSPACE), (HSPACE, 0), label=tex(STR_WEIGHT + "_0"), label_position=0.3)
nnfig.draw_synapse(ax, (0,  VSPACE), (HSPACE, 0), label=tex(STR_WEIGHT + "_1"), label_position=0.3)
nnfig.draw_synapse(ax, (0,       0), (HSPACE, 0), label=tex(STR_WEIGHT + "_2"), label_position=0.3)
nnfig.draw_synapse(ax, (0, -VSPACE), (HSPACE, 0), label=tex(STR_WEIGHT + "_3"), label_position=0.3, label_offset_y=-0.8)

nnfig.draw_synapse(ax, (HSPACE, 0), (HSPACE + 2, 0))

# Neuron ######################################

# Layer 1 (input)
#nnfig.draw_neuron(ax, (0,2*VSPACE), 0.5, empty=True)
nnfig.draw_neuron(ax, (0,  VSPACE), 0.5, empty=True)
nnfig.draw_neuron(ax, (0,       0), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, -VSPACE), 0.5, empty=True)

# Layer 2
nnfig.draw_neuron(ax, (HSPACE, 0), 1, ag_func="sum", tr_func="sigmoid")

# Text ########################################

# Layer 1 (input)
#plt.text(x=0.5, y=VSPACE+1, s=tex(STR_SIGOUT + "_i"), fontsize=12)

#plt.text(x=-1.7, y=2*VSPACE,    s=tex("1"), fontsize=12)
plt.text(x=-1.7, y=VSPACE,      s=tex(STR_SIGIN + "_1"), fontsize=12)
plt.text(x=-1.7, y=-0.2,        s=tex(STR_SIGIN + "_2"), fontsize=12)
plt.text(x=-1.7, y=-VSPACE-0.2, s=tex(STR_SIGIN + "_3"), fontsize=12)

# Layer 2
#plt.text(x=HSPACE-1.25, y=1.5, s=tex(STR_POT), fontsize=12)
#plt.text(x=2*HSPACE+0.4,  y=1.5, s=tex(STR_SIGOUT + "_o"), fontsize=12)

plt.text(x=HSPACE+2.5,  y=-0.3,
         s=tex(STR_SIGOUT),
         fontsize=12)

plt.show()
fig, ax = nnfig.init_figure(size_x=8, size_y=6)

HSPACE = 6
VSPACE = 4

# Synapse #####################################

nnfig.draw_synapse(ax, (0,2*VSPACE), (HSPACE, 0), label=tex(STR_WEIGHT + "_0"), label_position=0.3)
nnfig.draw_synapse(ax, (0,  VSPACE), (HSPACE, 0), label=tex(STR_WEIGHT + "_1"), label_position=0.3)
nnfig.draw_synapse(ax, (0,       0), (HSPACE, 0), label=tex(STR_WEIGHT + "_2"), label_position=0.3)
nnfig.draw_synapse(ax, (0, -VSPACE), (HSPACE, 0), label=tex(STR_WEIGHT + "_3"), label_position=0.3, label_offset_y=-0.8)

nnfig.draw_synapse(ax, (HSPACE, 0), (HSPACE + 2, 0))

# Neuron ######################################

# Layer 1 (input)
nnfig.draw_neuron(ax, (0,2*VSPACE), 0.5, empty=True)
nnfig.draw_neuron(ax, (0,  VSPACE), 0.5, empty=True)
nnfig.draw_neuron(ax, (0,       0), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, -VSPACE), 0.5, empty=True)

# Layer 2
nnfig.draw_neuron(ax, (HSPACE, 0), 1, ag_func="sum", tr_func="sigmoid")

# Text ########################################

# Layer 1 (input)
#plt.text(x=0.5, y=VSPACE+1, s=tex(STR_SIGOUT + "_i"), fontsize=12)

plt.text(x=-1.7, y=2*VSPACE,    s=tex("1"), fontsize=12)
plt.text(x=-1.7, y=VSPACE,      s=tex(STR_SIGIN + "_1"), fontsize=12)
plt.text(x=-1.7, y=-0.2,        s=tex(STR_SIGIN + "_2"), fontsize=12)
plt.text(x=-1.7, y=-VSPACE-0.2, s=tex(STR_SIGIN + "_3"), fontsize=12)

# Layer 2
#plt.text(x=HSPACE-1.25, y=1.5, s=tex(STR_POT), fontsize=12)
#plt.text(x=2*HSPACE+0.4,  y=1.5, s=tex(STR_SIGOUT + "_o"), fontsize=12)

plt.text(x=HSPACE+2.5,  y=-0.3,
         s=tex(STR_SIGOUT),
         fontsize=12)

plt.show()<jupyter_output><empty_output><jupyter_text>Exemple<jupyter_code>fig, ax = nnfig.init_figure(size_x=8, size_y=6)

HSPACE = 6
VSPACE = 4

# Synapse #####################################

nnfig.draw_synapse(ax, (0,2*VSPACE), (HSPACE, 0), label=tex(STR_WEIGHT + "_0"), label_position=0.3)
nnfig.draw_synapse(ax, (0,  VSPACE), (HSPACE, 0), label=tex(STR_WEIGHT + "_1"), label_position=0.3)
nnfig.draw_synapse(ax, (0,       0), (HSPACE, 0), label=tex(STR_WEIGHT + "_2"), label_position=0.3)
nnfig.draw_synapse(ax, (0, -VSPACE), (HSPACE, 0), label=tex(STR_WEIGHT + "_3"), label_position=0.3, label_offset_y=-0.8)

nnfig.draw_synapse(ax, (HSPACE, 0), (HSPACE + 2, 0))

# Neuron ######################################

# Layer 1 (input)
nnfig.draw_neuron(ax, (0,2*VSPACE), 0.5, empty=True)
nnfig.draw_neuron(ax, (0,  VSPACE), 0.5, empty=True)
nnfig.draw_neuron(ax, (0,       0), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, -VSPACE), 0.5, empty=True)

# Layer 2
nnfig.draw_neuron(ax, (HSPACE, 0), 1, ag_func="sum", tr_func="sigmoid")

# Text ########################################

# Layer 1 (input)
#plt.text(x=0.5, y=VSPACE+1, s=tex(STR_SIGOUT + "_i"), fontsize=12)

plt.text(x=-1.7, y=2*VSPACE,    s=tex("1"), fontsize=12)
plt.text(x=-1.7, y=VSPACE,      s=tex(STR_SIGIN + "_1"), fontsize=12)
plt.text(x=-1.7, y=-0.2,        s=tex(STR_SIGIN + "_2"), fontsize=12)
plt.text(x=-1.7, y=-VSPACE-0.2, s=tex(STR_SIGIN + "_3"), fontsize=12)

# Layer 2
#plt.text(x=HSPACE-1.25, y=1.5, s=tex(STR_POT), fontsize=12)
#plt.text(x=2*HSPACE+0.4,  y=1.5, s=tex(STR_SIGOUT + "_o"), fontsize=12)

plt.text(x=HSPACE+2.5,  y=-0.3,
         s=tex(STR_SIGOUT),
         fontsize=12)

plt.show()<jupyter_output><empty_output><jupyter_text>Pour vecteur d'entrée = ... et un vecteur de poids arbitrairement fixé à ...et un neurone défini avec la fonction sigmoïde, on peut calculer la valeur de sortie du neurone :On a:$$\sum_i \weight_i \feature_i = \dots$$donc$$y = \frac{1}{1 + e^{-\dots}} $$<jupyter_code>@interact(w1=(-10., 10., 0.5), w2=(-10., 10., 0.5))
def nn1(wb1=0., w1=10.):
    x = np.linspace(-10., 10., 100)
    xb = np.ones(x.shape)

    s1 = wb1 * xb + w1 * x
    y = sigmoid(s1)

    plt.plot(x, y)<jupyter_output><empty_output><jupyter_text>Définition d'un réseau de neurones  Disposition des neurones en couches et couches cachéesTODO  Exemple : réseau de neurones à 1 couche "cachée"<jupyter_code>fig, ax = nnfig.init_figure(size_x=8, size_y=4)

HSPACE = 6
VSPACE = 4

# Synapse #####################################

# Layer 1-2
nnfig.draw_synapse(ax, (0,  VSPACE), (HSPACE,  VSPACE), label=tex(STR_WEIGHT + "_1"), label_position=0.4)
nnfig.draw_synapse(ax, (0, -VSPACE), (HSPACE,  VSPACE), label=tex(STR_WEIGHT + "_3"), label_position=0.25, label_offset_y=-0.8)

nnfig.draw_synapse(ax, (0,  VSPACE), (HSPACE, -VSPACE), label=tex(STR_WEIGHT + "_2"), label_position=0.25)
nnfig.draw_synapse(ax, (0, -VSPACE), (HSPACE, -VSPACE), label=tex(STR_WEIGHT + "_4"), label_position=0.4, label_offset_y=-0.8)

# Layer 2-3
nnfig.draw_synapse(ax, (HSPACE,  VSPACE), (2*HSPACE, 0), label=tex(STR_WEIGHT + "_5"), label_position=0.4)
nnfig.draw_synapse(ax, (HSPACE, -VSPACE), (2*HSPACE, 0), label=tex(STR_WEIGHT + "_6"), label_position=0.4, label_offset_y=-0.8)

nnfig.draw_synapse(ax, (2*HSPACE, 0), (2*HSPACE + 2, 0))

# Neuron ######################################

# Layer 1 (input)
nnfig.draw_neuron(ax, (0,  VSPACE), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, -VSPACE), 0.5, empty=True)

# Layer 2
nnfig.draw_neuron(ax, (HSPACE,  VSPACE), 1, ag_func="sum", tr_func="sigmoid")
nnfig.draw_neuron(ax, (HSPACE, -VSPACE), 1, ag_func="sum", tr_func="sigmoid")

# Layer 3
nnfig.draw_neuron(ax, (2*HSPACE, 0), 1, ag_func="sum", tr_func="sigmoid")

# Text ########################################

# Layer 1 (input)
#plt.text(x=0.5, y=VSPACE+1, s=tex(STR_SIGOUT + "_i"), fontsize=12)
plt.text(x=-1.7, y=VSPACE,      s=tex(STR_SIGIN + "_1"), fontsize=12)
plt.text(x=-1.7, y=-VSPACE-0.2, s=tex(STR_SIGIN + "_2"), fontsize=12)

# Layer 2
#plt.text(x=HSPACE-1.25, y=VSPACE+1.5, s=tex(STR_POT + "_1"), fontsize=12)
plt.text(x=HSPACE+0.4,  y=VSPACE+1.5, s=tex(STR_SIGOUT + "_1"), fontsize=12)

#plt.text(x=HSPACE-1.25, y=-VSPACE-1.8, s=tex(STR_POT + "_2"), fontsize=12)
plt.text(x=HSPACE+0.4,  y=-VSPACE-1.8, s=tex(STR_SIGOUT + "_2"), fontsize=12)

# Layer 3
#plt.text(x=2*HSPACE-1.25, y=1.5, s=tex(STR_POT + "_o"), fontsize=12)
#plt.text(x=2*HSPACE+0.4,  y=1.5, s=tex(STR_SIGOUT + "_o"), fontsize=12)

plt.text(x=2*HSPACE+2.5,  y=-0.3,
         s=tex(STR_SIGOUT),
         fontsize=12)

plt.show()<jupyter_output><empty_output><jupyter_text>TODO: il manque les biais... $$\sigout =\activfunc \left(\weight_5 ~ \underbrace{\activfunc \left(\weight_1 \feature_1 + \weight_3 \feature_2 \right)}_{\sigout_1}+\weight_6 ~ \underbrace{\activfunc \left(\weight_2 \feature_1 + \weight_4 \feature_2 \right)}_{\sigout_2}\right)$$<jupyter_code>@interact(wb1=(-10., 10., 0.5), w1=(-10., 10., 0.5), wb2=(-10., 10., 0.5), w2=(-10., 10., 0.5))
def nn1(wb1=0.1, w1=0.1, wb2=0.1, w2=0.1):
    x = np.linspace(-10., 10., 100)
    xb = np.ones(x.shape)

    s1 = wb1 * xb + w1 * x
    y1 = sigmoid(s1)
    
    s2 = wb2 * xb + w2 * x
    y2 = sigmoid(s2)
    
    s = wb2 * xb + w2 * x
    y = sigmoid(s)

    plt.plot(x, y)<jupyter_output><empty_output><jupyter_text>Exemple : réseau de neurones à 2 couches "cachée"<jupyter_code>fig, ax = nnfig.init_figure(size_x=8, size_y=4)

HSPACE = 6
VSPACE = 4

# Synapse #####################################

# Layer 1-2
nnfig.draw_synapse(ax, (0,  VSPACE), (HSPACE,  VSPACE), label=tex(STR_WEIGHT + "_1"), label_position=0.4)
nnfig.draw_synapse(ax, (0, -VSPACE), (HSPACE,  VSPACE), label=tex(STR_WEIGHT + "_3"), label_position=0.25, label_offset_y=-0.8)

nnfig.draw_synapse(ax, (0,  VSPACE), (HSPACE, -VSPACE), label=tex(STR_WEIGHT + "_2"), label_position=0.25)
nnfig.draw_synapse(ax, (0, -VSPACE), (HSPACE, -VSPACE), label=tex(STR_WEIGHT + "_4"), label_position=0.4, label_offset_y=-0.8)

# Layer 2-3
nnfig.draw_synapse(ax, (HSPACE,  VSPACE), (2*HSPACE,  VSPACE), label=tex(STR_WEIGHT + "_5"), label_position=0.4)
nnfig.draw_synapse(ax, (HSPACE, -VSPACE), (2*HSPACE,  VSPACE), label=tex(STR_WEIGHT + "_7"), label_position=0.25, label_offset_y=-0.8)

nnfig.draw_synapse(ax, (HSPACE,  VSPACE), (2*HSPACE, -VSPACE), label=tex(STR_WEIGHT + "_6"), label_position=0.25)
nnfig.draw_synapse(ax, (HSPACE, -VSPACE), (2*HSPACE, -VSPACE), label=tex(STR_WEIGHT + "_8"), label_position=0.4, label_offset_y=-0.8)

# Layer 3-4
nnfig.draw_synapse(ax, (2*HSPACE,  VSPACE), (3*HSPACE, 0), label=tex(STR_WEIGHT + "_9"), label_position=0.4)
nnfig.draw_synapse(ax, (2*HSPACE, -VSPACE), (3*HSPACE, 0), label=tex(STR_WEIGHT + "_{10}"), label_position=0.4, label_offset_y=-0.8)

nnfig.draw_synapse(ax, (3*HSPACE, 0), (3*HSPACE + 2, 0))

# Neuron ######################################

# Layer 1 (input)
nnfig.draw_neuron(ax, (0,  VSPACE), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, -VSPACE), 0.5, empty=True)

# Layer 2
nnfig.draw_neuron(ax, (HSPACE,  VSPACE), 1, ag_func="sum", tr_func="sigmoid")
nnfig.draw_neuron(ax, (HSPACE, -VSPACE), 1, ag_func="sum", tr_func="sigmoid")

# Layer 3
nnfig.draw_neuron(ax, (2*HSPACE,  VSPACE), 1, ag_func="sum", tr_func="sigmoid")
nnfig.draw_neuron(ax, (2*HSPACE, -VSPACE), 1, ag_func="sum", tr_func="sigmoid")

# Layer 4
nnfig.draw_neuron(ax, (3*HSPACE, 0), 1, ag_func="sum", tr_func="sigmoid")

# Text ########################################

# Layer 1 (input)
#plt.text(x=0.5, y=VSPACE+1, s=tex(STR_SIGOUT + "_i"), fontsize=12)
plt.text(x=-1.7, y=VSPACE,      s=tex(STR_SIGIN + "_1"), fontsize=12)
plt.text(x=-1.7, y=-VSPACE-0.2, s=tex(STR_SIGIN + "_2"), fontsize=12)

# Layer 2
#plt.text(x=HSPACE-1.25, y=VSPACE+1.5, s=tex(STR_POT + "_1"), fontsize=12)
plt.text(x=HSPACE+0.4,  y=VSPACE+1.5, s=tex(STR_SIGOUT + "_1"), fontsize=12)

#plt.text(x=HSPACE-1.25, y=-VSPACE-1.8, s=tex(STR_POT + "_2"), fontsize=12)
plt.text(x=HSPACE+0.4,  y=-VSPACE-1.8, s=tex(STR_SIGOUT + "_2"), fontsize=12)

# Layer 3
#plt.text(x=2*HSPACE-1.25, y=VSPACE+1.5, s=tex(STR_POT + "_3"), fontsize=12)
plt.text(x=2*HSPACE+0.4,  y=VSPACE+1.5, s=tex(STR_SIGOUT + "_3"), fontsize=12)

#plt.text(x=2*HSPACE-1.25, y=-VSPACE-1.8, s=tex(STR_POT + "_4"), fontsize=12)
plt.text(x=2*HSPACE+0.4,  y=-VSPACE-1.8, s=tex(STR_SIGOUT + "_4"), fontsize=12)

# Layer 4
#plt.text(x=3*HSPACE-1.25, y=1.5, s=tex(STR_POT + "_o"), fontsize=12)
#plt.text(x=3*HSPACE+0.4,  y=1.5, s=tex(STR_SIGOUT + "_o"), fontsize=12)

plt.text(x=3*HSPACE+2.5,  y=-0.3,
         s=tex(STR_SIGOUT),
         fontsize=12)

plt.show()<jupyter_output><empty_output><jupyter_text>TODO: il manque le biais...$\newcommand{\yone}{\underbrace{\activfunc \left(\weight_1 \feature_1 + \weight_3 \feature_2 \right)}_{\sigout_1}}\newcommand{\ytwo}{\underbrace{\activfunc \left(\weight_2 \feature_1 + \weight_4 \feature_2 \right)}_{\sigout_2}}\newcommand{\ythree}{\underbrace{\activfunc \left(\weight_5 \yone + \weight_7 \ytwo \right)}_{\sigout_3}}\newcommand{\yfour}{\underbrace{\activfunc \left(\weight_6 \yone + \weight_8 \ytwo \right)}_{\sigout_4}}$$$\sigout =\activfunc \left(\weight_9 ~ \ythree+\weight_{10} ~ \yfour\right)$$  Pouvoir expressif d'un réseau de neurones TODO  Apprentissage  Fonction objectif (ou fonction d'erreur) Fonction objectif: $\errfunc \left( \weights \right)$ Typiquement, la fonction objectif (fonction d'erreur) est la somme du carré de l'erreur de chaque neurone de sortie. $$\errfunc = \frac12 \sum_{\cur \in \Omega} \left[ \sigout_\cur - \sigoutdes_\cur \right]^2$$$\Omega$: l'ensemble des neurones de sortieLe $\frac12$, c'est juste pour simplifier les calculs de la dérivée.<jupyter_code>fig, ax = nnfig.init_figure(size_x=8, size_y=4)

nnfig.draw_synapse(ax, (0, -6), (10, 0))
nnfig.draw_synapse(ax, (0, -2), (10, 0))
nnfig.draw_synapse(ax, (0, 2),  (10, 0))
nnfig.draw_synapse(ax, (0, 6),  (10, 0))

nnfig.draw_synapse(ax, (0, -6), (10, -4))
nnfig.draw_synapse(ax, (0, -2), (10, -4))
nnfig.draw_synapse(ax, (0, 2),  (10, -4))
nnfig.draw_synapse(ax, (0, 6),  (10, -4))

nnfig.draw_synapse(ax, (0, -6), (10, 4))
nnfig.draw_synapse(ax, (0, -2), (10, 4))
nnfig.draw_synapse(ax, (0, 2),  (10, 4))
nnfig.draw_synapse(ax, (0, 6),  (10, 4))

nnfig.draw_synapse(ax, (10, -4), (12, -4))
nnfig.draw_synapse(ax, (10, 0), (12, 0))
nnfig.draw_synapse(ax, (10, 4), (12, 4))

nnfig.draw_neuron(ax, (0, -6), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, -2), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, 2),  0.5, empty=True)
nnfig.draw_neuron(ax, (0, 6),  0.5, empty=True)

nnfig.draw_neuron(ax, (10, -4), 1, ag_func="sum", tr_func="sigmoid")
nnfig.draw_neuron(ax, (10, 0),  1, ag_func="sum", tr_func="sigmoid")
nnfig.draw_neuron(ax, (10, 4),  1, ag_func="sum", tr_func="sigmoid")

plt.text(x=0, y=7.5, s=tex(STR_PREV), fontsize=14)
plt.text(x=10, y=7.5, s=tex(STR_CUR), fontsize=14)

plt.text(x=0,   y=0,    s=r"$\vdots$", fontsize=14)
plt.text(x=9.7, y=-6.1, s=r"$\vdots$", fontsize=14)
plt.text(x=9.7, y=5.8,  s=r"$\vdots$", fontsize=14)

plt.text(x=12.5, y=4,  s=tex(STR_SIGOUT + "_1"), fontsize=14)
plt.text(x=12.5, y=0,  s=tex(STR_SIGOUT + "_2"), fontsize=14)
plt.text(x=12.5, y=-4, s=tex(STR_SIGOUT + "_3"), fontsize=14)

plt.text(x=16, y=4,  s=tex(STR_ERRFUNC + "_1 = " + STR_SIGOUT + "_1 - " + STR_SIGOUT_DES + "_1"), fontsize=14)
plt.text(x=16, y=0,  s=tex(STR_ERRFUNC + "_2 = " + STR_SIGOUT + "_2 - " + STR_SIGOUT_DES + "_2"), fontsize=14)
plt.text(x=16, y=-4, s=tex(STR_ERRFUNC + "_3 = " + STR_SIGOUT + "_3 - " + STR_SIGOUT_DES + "_3"), fontsize=14)

plt.text(x=16, y=-8, s=tex(STR_ERRFUNC + " = 1/2 ( " + STR_ERRFUNC + "^2_1 + " + STR_ERRFUNC + "^2_2 + " + STR_ERRFUNC + "^2_3 + \dots )"), fontsize=14)

plt.show()<jupyter_output><empty_output><jupyter_text>Mise à jours des poids $$\weights_{\learnit + 1} = \weights_{\learnit} - \underbrace{\learnrate \nabla_{\weights} \errfunc \left( \weights_{\learnit} \right)}$$$- \learnrate \nabla_{\weights} \errfunc \left( \weights_{\learnit} \right)$: descend dans la direction opposée au gradient (plus forte pente)avec $\nabla_{\weights} \errfunc \left( \weights_{\learnit} \right)$: gradient de la fonction objectif au point $\weights$$\learnrate > 0$: pas (ou taux) d'apprentissage$$\begin{align}\delta_{\wcur} & = \wcur_{\learnit + 1} - \wcur_{\learnit} \\               & = - \learnrate \frac{\partial \errfunc}{\partial \wcur}\end{align}$$$$\Leftrightarrow \wcur_{\learnit + 1} = \wcur_{\learnit} - \learnrate \frac{\partial \errfunc}{\partial \wcur}$$ Chaque présentation de l'ensemble des exemples = un *cycle* (ou une *époque*) d'apprentissageCritère d'arrêt de l'apprentissage: quand la valeur de la fonction objectif se stabilise (ou que le problème est résolu avec la précision souhaitée)  Dérivée des principales fonctions d'activation Fonction sigmoïdeFonction dérivée :$$f'(x) = \frac{\lambda e^{-\lambda x}}{(1+e^{-\lambda x})^{2}}$$qui peut aussi être défini par$$\frac{\mathrm{d} y}{\mathrm{d} x} = \lambda y (1-y)$$où $y$ varie de 0 à 1.<jupyter_code>def d_sigmoid(x, _lambda=1.):
    e = np.exp(-_lambda * x)
    y = _lambda * e / np.power(1 + e, 2)
    return y
%matplotlib inline

x = np.linspace(-5, 5, 300)

y1 = d_sigmoid(x, 1.)
y2 = d_sigmoid(x, 5.)
y3 = d_sigmoid(x, 0.5)

plt.plot(x, y1, label=r"$\lambda=1$")
plt.plot(x, y2, label=r"$\lambda=5$")
plt.plot(x, y3, label=r"$\lambda=0.5$")

plt.hlines(y=0, xmin=-5, xmax=5, color='gray', linestyles='dotted')
plt.vlines(x=0, ymin=-2, ymax=2, color='gray', linestyles='dotted')

plt.legend()

plt.title("Fonction dérivée de la sigmoïde")
plt.axis([-5, 5, -0.5, 2]);<jupyter_output><empty_output><jupyter_text>Tangente hyperboliqueDérivée :$$\tanh '= \frac{1}{\cosh^{2}} = 1-\tanh^{2}$$<jupyter_code>def d_tanh(x):
    y = 1. - np.power(np.tanh(x), 2)
    return y
x = np.linspace(-5, 5, 300)
y = d_tanh(x)

plt.plot(x, y)

plt.hlines(y=0, xmin=-5, xmax=5, color='gray', linestyles='dotted')
plt.vlines(x=0, ymin=-2, ymax=2, color='gray', linestyles='dotted')

plt.title("Fonction dérivée de la tangente hyperbolique")
plt.axis([-5, 5, -2, 2]);
# TODO
# - "généralement le minimum local suffit" (preuve ???)
# - "dans le cas contraire, le plus simple est de recommencer plusieurs fois l'apprentissage avec des poids initiaux différents et de conserver la meilleure matrice $\weights$ (celle qui minimise $\errfunc$)"
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(4, 4))

x = np.arange(10, 30, 0.1)
y = (x - 20)**2 + 2

ax.set_xlabel(r"Poids $" + STR_WEIGHTS + "$", fontsize=14)
ax.set_ylabel(r"Fonction objectif $" + STR_ERRFUNC + "$", fontsize=14)

# See http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.tick_params
ax.tick_params(axis='both',       # changes apply to the x and y axis
               which='both',      # both major and minor ticks are affected
               bottom='on',       # ticks along the bottom edge are on
               top='off',         # ticks along the top edge are off
               left='on',         # ticks along the left edge are on
               right='off',       # ticks along the right edge are off
               labelbottom='off', # labels along the bottom edge are off
               labelleft='off')   # labels along the lefleft are off

ax.set_xlim(left=10, right=25)
ax.set_ylim(bottom=0, top=5)

ax.plot(x, y);<jupyter_output><empty_output><jupyter_text>*Apprentissage incrémentiel* (ou *partiel*) (ang. *incremental learning*):on ajuste les poids $\weights$ après la présentation d'un seul exemple("ce n'est pas une véritable descente de gradient").C'est mieux pour éviter les minimums locaux, surtout si les exemples sontmélangés au début de chaque itération<jupyter_code># *Apprentissage différé* (ang. *batch learning*):
# TODO
# Est-ce que la fonction objectif $\errfunc$ est une fonction multivariée
# ou est-ce une aggrégation des erreurs de chaque exemple ?
# **TODO: règle du delta / règle du delta généralisée**<jupyter_output><empty_output><jupyter_text>Rétropropagation du gradient *Rétropropagation du gradient*:une méthode pour calculer efficacement le gradient de la fonction objectif $\errfunc$.Intuition:La *rétropropagation du gradient* n'est qu'une méthode parmis d'autre pour résoudre le probème d'optimisation des poids $\weight$. On pourrait très bien résoudre ce problème d'optimisation avec des algorithmes évolutionnistes par exemple.En fait, l'intérêt de la méthode de la *rétropropagation du gradient* (et ce qui explique sa notoriété) est qu'elle formule le problème d'optimisation des poids avec une écriture analytique particulièrement efficace qui élimine astucieusement un grand nombre de calculs redondants (un peu à la manière de ce qui se fait en programmation dynamique): quand on decide d'optimiser les poids via une *descente de gradient*, certains termes (les *signaux d'erreurs* $\errsig$) apparaissent un grand nombre de fois dans l'écriture analytique complète du gradient. La méthode de la retropropagation du gradient fait en sorte que ces termes ne soient calculés qu'une seule fois.À noter qu'on aurrait très bien pu résoudre le problème avec une descente de gradient oú le gradient $\frac{\partial \errfunc}{\partial\wcur_{\learnit}}$ serait calculé via une approximation numérique (*méthode des différences finies* par exemple) mais ce serait beaucoup plus lent et beaucoup moins efficace...Principe:on modifie les poids à l'aide des *signaux d'erreur* $\errsig$.$$\wcur_{\learnit + 1} = \wcur_{\learnit} \underbrace{- \learnrate \frac{\partial \errfunc}{\partial \wcur_{\learnit}}}_{\delta_\prevcur}$$$$\begin{align}\delta_\prevcur & = - \learnrate \frac{\partial \errfunc}{\partial \wcur(\learnit)} \\                & = - \learnrate \errsig_\cur \sigout\prev\end{align}$$- Dans le cas de *l'apprentissage différé* (*batch*), on calcule pour chaque exemple l'erreur correspondante. Leur contribution individuelle aux modifications des poids sont additionnées- L'apprentissage suppervisé fonctionne mieux avec des neurones de sortie linéaires (fonction d'activation $\activfunc$ = fonction identitée) "car les signaux d'erreurs se transmettent mieux".- Des données d'entrée binaires doivent être choisies dans $\{-1,1\}$ plutôt que $\{0,1\}$ car un signal nul ne contribu pas à l'apprentissage.<jupyter_code># TODO
#Voc:
#- *erreur marginale*: **TODO**<jupyter_output><empty_output><jupyter_text>Note intéressante de <NAME> : http://people.idsia.ch/~juergen/who-invented-backpropagation.html  Signaux d'erreur $\errsig_\cur$ pour les neurones de sortie $(\cur \in \Omega)$ $$\errsig_\cur = \activfunc'(\pot_\cur)[\sigout_\cur - \sigoutdes_\cur]$$  Signaux d'erreur $\errsig_\cur$ pour les neurones cachés $(\cur \not\in \Omega)$ $$\errsig_\cur = \activfunc'(\pot_\cur) \sum_\next \weight_\curnext \errsig_\next$$<jupyter_code>fig, ax = nnfig.init_figure(size_x=8, size_y=4)

nnfig.draw_synapse(ax, (0, -2), (10, 0))
nnfig.draw_synapse(ax, (0, 2),  (10, 0), label=tex(STR_WEIGHT + "_{" + STR_NEXT + STR_CUR + "}"), label_position=0.5, fontsize=14)

nnfig.draw_synapse(ax, (10, 0), (12, 0))

nnfig.draw_neuron(ax, (0, -2), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, 2),  0.5, empty=True)

plt.text(x=0, y=3.5, s=tex(STR_CUR), fontsize=14)
plt.text(x=10, y=3.5, s=tex(STR_NEXT), fontsize=14)
plt.text(x=0, y=-0.2, s=r"$\vdots$", fontsize=14)

nnfig.draw_neuron(ax, (10, 0), 1, ag_func="sum", tr_func="sigmoid")

plt.show()<jupyter_output><empty_output><jupyter_text>Plus de détail : calcul de $\errsig_\cur$ Dans l'exemple suivant on ne s'intéresse qu'aux poids $\weight_1$, $\weight_2$, $\weight_3$, $\weight_4$ et $\weight_5$ pour simplifier la demonstration.<jupyter_code>fig, ax = nnfig.init_figure(size_x=8, size_y=4)

HSPACE = 6
VSPACE = 4

# Synapse #####################################

# Layer 1-2
nnfig.draw_synapse(ax, (0,  VSPACE), (HSPACE,  VSPACE), label=tex(STR_WEIGHT + "_1"), label_position=0.4)
nnfig.draw_synapse(ax, (0, -VSPACE), (HSPACE,  VSPACE), color="lightgray")

nnfig.draw_synapse(ax, (0,  VSPACE), (HSPACE, -VSPACE), color="lightgray")
nnfig.draw_synapse(ax, (0, -VSPACE), (HSPACE, -VSPACE), color="lightgray")

# Layer 2-3
nnfig.draw_synapse(ax, (HSPACE,  VSPACE), (2*HSPACE,  VSPACE), label=tex(STR_WEIGHT + "_2"), label_position=0.4)
nnfig.draw_synapse(ax, (HSPACE, -VSPACE), (2*HSPACE,  VSPACE), color="lightgray")

nnfig.draw_synapse(ax, (HSPACE,  VSPACE), (2*HSPACE, -VSPACE), label=tex(STR_WEIGHT + "_3"), label_position=0.4)
nnfig.draw_synapse(ax, (HSPACE, -VSPACE), (2*HSPACE, -VSPACE), color="lightgray")

# Layer 3-4
nnfig.draw_synapse(ax, (2*HSPACE,  VSPACE), (3*HSPACE, 0), label=tex(STR_WEIGHT + "_4"), label_position=0.4)
nnfig.draw_synapse(ax, (2*HSPACE, -VSPACE), (3*HSPACE, 0), label=tex(STR_WEIGHT + "_5"), label_position=0.4, label_offset_y=-0.8)

# Neuron ######################################

# Layer 1 (input)
nnfig.draw_neuron(ax, (0,  VSPACE), 0.5, empty=True)
nnfig.draw_neuron(ax, (0, -VSPACE), 0.5, empty=True, line_color="lightgray")

# Layer 2
nnfig.draw_neuron(ax, (HSPACE,  VSPACE), 1, ag_func="sum", tr_func="sigmoid")
nnfig.draw_neuron(ax, (HSPACE, -VSPACE), 1, ag_func="sum", tr_func="sigmoid", line_color="lightgray")

# Layer 3
nnfig.draw_neuron(ax, (2*HSPACE,  VSPACE), 1, ag_func="sum", tr_func="sigmoid")
nnfig.draw_neuron(ax, (2*HSPACE, -VSPACE), 1, ag_func="sum", tr_func="sigmoid")

# Layer 4
nnfig.draw_neuron(ax, (3*HSPACE, 0), 1, ag_func="sum", tr_func="sigmoid")

# Text ########################################

# Layer 1 (input)
plt.text(x=0.5, y=VSPACE+1, s=tex(STR_SIGOUT + "_i"), fontsize=12)

# Layer 2
plt.text(x=HSPACE-1.25, y=VSPACE+1.5, s=tex(STR_POT + "_1"), fontsize=12)
plt.text(x=HSPACE+0.4,  y=VSPACE+1.5, s=tex(STR_SIGOUT + "_1"), fontsize=12)

# Layer 3
plt.text(x=2*HSPACE-1.25, y=VSPACE+1.5, s=tex(STR_POT + "_2"), fontsize=12)
plt.text(x=2*HSPACE+0.4,  y=VSPACE+1.5, s=tex(STR_SIGOUT + "_2"), fontsize=12)

plt.text(x=2*HSPACE-1.25, y=-VSPACE-1.8, s=tex(STR_POT + "_3"), fontsize=12)
plt.text(x=2*HSPACE+0.4,  y=-VSPACE-1.8, s=tex(STR_SIGOUT + "_3"), fontsize=12)

# Layer 4
plt.text(x=3*HSPACE-1.25, y=1.5, s=tex(STR_POT + "_o"), fontsize=12)
plt.text(x=3*HSPACE+0.4,  y=1.5, s=tex(STR_SIGOUT + "_o"), fontsize=12)

plt.text(x=3*HSPACE+2,  y=-0.3,
         s=tex(STR_ERRFUNC + " = (" + STR_SIGOUT + "_o - " + STR_SIGOUT_DES + "_o)^2/2"),
         fontsize=12)

plt.show()<jupyter_output><empty_output><jupyter_text>**Attention**: $\weight_1$ influe $\pot_2$ **et** $\pot_3$ en plus de $\pot_1$ et $\pot_o$.  Calcul de la dérivée partielle de l'erreur par rapport au poid synaptique $\weight_4$ rappel: $$\begin{align}\errfunc  &= \frac12 \left( \sigout_o - \sigoutdes_o \right)^2 \tag{1} \\\sigout_o &= \activfunc(\pot_o)  \tag{2} \\\pot_o    &= \sigout_2 \weight_4 + \sigout_3 \weight_5  \tag{3} \\\end{align}$$ c'est à dire: $$\errfunc = \frac12 \left( \activfunc \left( \sigout_2 \weight_4 + \sigout_3 \weight_5 \right) - \sigoutdes_o \right)^2$$ donc, en appliquant les règles de derivation de fonctions composées, on a: $$\frac{\partial \errfunc}{\partial \weight_4} =\frac{\partial \pot_o}{\partial \weight_4}\underbrace{\frac{\partial \sigout_o}{\partial \pot_o}\frac{\partial \errfunc}{\partial \sigout_o}}_{\errsig_o}$$ Rappel: dérivation des fonctions composées (parfois appelé règle de dérivation en chaîne ou règle de la chaîne)$$\frac{\mathrm{d} y}{\mathrm{d} x} = \frac{\mathrm{d} y}{\mathrm{d} u} \cdot \frac{\mathrm{d} u}{\mathrm {d} x}$$ de (1), (2) et (3) on déduit: $$\begin{align}\frac{\partial \pot_o}{\partial \weight_4}   &= \sigout_2 \\\frac{\partial \sigout_o}{\partial \pot_o}   &= \activfunc'(\pot_o) \\\frac{\partial \errfunc}{\partial \sigout_o} &= \sigout_o - \sigoutdes_o \\\end{align}$$ le signal d'erreur s'écrit donc: $$\begin{align}\errsig_o &=\frac{\partial \sigout_o}{\partial \pot_o}\frac{\partial \errfunc}{\partial \sigout_o} \\&= \activfunc'(\pot_o) [\sigout_o - \sigoutdes_o]\end{align}$$  Calcul de la dérivée partielle de l'erreur par rapport au poid synaptique $\weight_5$ $$\frac{\partial \errfunc}{\partial \weight_5} =\frac{\partial \pot_o}{\partial \weight_5}\underbrace{\frac{\partial \sigout_o}{\partial \pot_o}\frac{\partial \errfunc}{\partial \sigout_o}}_{\errsig_o}$$ avec: $$\begin{align}\frac{\partial \pot_o}{\partial \weight_5}   &= \sigout_3 \\\frac{\partial \sigout_o}{\partial \pot_o}   &= \activfunc'(\pot_o) \\\frac{\partial \errfunc}{\partial \sigout_o} &= \sigout_o - \sigoutdes_o \\\errsig_o &=\frac{\partial \sigout_o}{\partial \pot_o}\frac{\partial \errfunc}{\partial \sigout_o} \\&= \activfunc'(\pot_o) [\sigout_o - \sigoutdes_o]\end{align}$$  Calcul de la dérivée partielle de l'erreur par rapport au poid synaptique $\weight_2$ $$\frac{\partial \errfunc}{\partial \weight_2} =\frac{\partial \pot_2}{\partial \weight_2}%\underbrace{ \frac{\partial \sigout_2}{\partial \pot_2} \frac{\partial \pot_o}{\partial \sigout_2} \underbrace{  \frac{\partial \sigout_o}{\partial \pot_o}  \frac{\partial \errfunc}{\partial \sigout_o} }_{\errsig_o}}_{\errsig_2}$$ avec: $$\begin{align}\frac{\partial \pot_2}{\partial \weight_2} &= \sigout_1 \\\frac{\partial \sigout_2}{\partial \pot_2} &= \activfunc'(\pot_2) \\\frac{\partial \pot_o}{\partial \sigout_2} &= \weight_4 \\\errsig_2 &=\frac{\partial \sigout_2}{\partial \pot_2}\frac{\partial \pot_o}{\partial \sigout_2}\errsig_o \\&= \activfunc'(\pot_2) \weight_4 \errsig_o\end{align}$$  Calcul de la dérivée partielle de l'erreur par rapport au poid synaptique $\weight_3$ $$\frac{\partial \errfunc}{\partial \weight_3} =\frac{\partial \pot_3}{\partial \weight_3}%\underbrace{ \frac{\partial \sigout_3}{\partial \pot_3} \frac{\partial \pot_o}{\partial \sigout_3} \underbrace{  \frac{\partial \sigout_o}{\partial \pot_o}  \frac{\partial \errfunc}{\partial \sigout_o} }_{\errsig_o}}_{\errsig_3}$$ avec: $$\begin{align}\frac{\partial \pot_3}{\partial \weight_3} &= \sigout_1 \\\frac{\partial \sigout_3}{\partial \pot_3} &= \activfunc'(\pot_3) \\\frac{\partial \pot_o}{\partial \sigout_3} &= \weight_5 \\\errsig_3 &= \frac{\partial \sigout_3}{\partial \pot_3}\frac{\partial \pot_o}{\partial \sigout_3}\errsig_o \\&= \activfunc'(\pot_3) \weight_5 \errsig_o\end{align}$$  Calcul de la dérivée partielle de l'erreur par rapport au poid synaptique $\weight_1$ $$\frac{\partial \errfunc}{\partial \weight_1} =\frac{\partial \pot_1}{\partial \weight_1}%\underbrace{ \frac{\partial \sigout_1}{\partial \pot_1} \left(  \frac{\partial \pot_2}{\partial \sigout_1} % err?  \underbrace{   \frac{\partial \sigout_2}{\partial \pot_2}   \frac{\partial \pot_o}{\partial \sigout_2}   \underbrace{    \frac{\partial \sigout_o}{\partial \pot_o}    \frac{\partial \errfunc}{\partial \sigout_o}   }_{\errsig_o}  }_{\errsig_2}  +  \frac{\partial \pot_3}{\partial \sigout_1} % err?  \underbrace{   \frac{\partial \sigout_3}{\partial \pot_3}   \frac{\partial \pot_o}{\partial \sigout_3}   \underbrace{    \frac{\partial \sigout_o}{\partial \pot_o}    \frac{\partial \errfunc}{\partial \sigout_o}   }_{\errsig_o}  }_{\errsig_3} \right)}_{\errsig_1}$$ avec: $$\begin{align}\frac{\partial \pot_1}{\partial \weight_1} &= \sigout_i \\\frac{\partial \sigout_1}{\partial \pot_1} &= \activfunc'(\pot_1) \\\frac{\partial \pot_2}{\partial \sigout_1} &= \weight_2 \\\frac{\partial \pot_3}{\partial \sigout_1} &= \weight_3 \\\errsig_1 &=\frac{\partial \sigout_1}{\partial \pot_1}\left(\frac{\partial \pot_2}{\partial \sigout_1}\errsig_2+\frac{\partial \pot_3}{\partial \sigout_1}\errsig_3\right) \\&= \activfunc'(\pot_1) \left( \weight_2 \errsig_2 + \weight_3 \errsig_3 \right)\end{align}$$  Python implementation<jupyter_code># Define the activation function and its derivative
activation_function = tanh
d_activation_function = d_tanh
def init_weights(num_input_cells, num_output_cells, num_cell_per_hidden_layer, num_hidden_layers=1):
    """
    The returned `weights` object is a list of weight matrices,
    where weight matrix at index $i$ represents the weights between
    layer $i$ and layer $i+1$.
    
    Numpy array shapes for e.g. num_input_cells=2, num_output_cells=2,
    num_cell_per_hidden_layer=3 (without taking account bias):
    - in:        (2,)
    - in+bias:   (3,)
    - w[0]:      (3,3)
    - w[0]+bias: (3,4)
    - w[1]:      (3,2)
    - w[1]+bias: (4,2)
    - out:       (2,)
    """
    
    # TODO:
    # - faut-il que wij soit positif ?
    # - loi normale plus appropriée que loi uniforme ?
    # - quel sigma conseillé ?
    
    W = []
    
    # Weights between the input layer and the first hidden layer
    W.append(np.random.uniform(low=0., high=1., size=(num_input_cells + 1, num_cell_per_hidden_layer + 1)))
    
    # Weights between hidden layers (if there are more than one hidden layer)
    for layer in range(num_hidden_layers - 1):
        W.append(np.random.uniform(low=0., high=1., size=(num_cell_per_hidden_layer + 1, num_cell_per_hidden_layer + 1)))
    
    # Weights between the last hidden layer and the output layer
    W.append(np.random.uniform(low=0., high=1., size=(num_cell_per_hidden_layer + 1, num_output_cells)))
    
    return W
def evaluate_network(weights, input_signal):      # TODO: find a better name
    
    # Add the bias on the input layer
    input_signal = np.concatenate([input_signal, [-1]])
    
    assert input_signal.ndim == 1
    assert input_signal.shape[0] == weights[0].shape[0]
    
    # Compute the output of the first hidden layer
    p = np.dot(input_signal, weights[0])
    output_hidden_layer = activation_function(p)
    
    # Compute the output of the intermediate hidden layers
    # TODO: check this
    num_layers = len(weights)
    for n in range(num_layers - 2):
        p = np.dot(output_hidden_layer, weights[n + 1])
        output_hidden_layer = activation_function(p)
    
    # Compute the output of the output layer
    p = np.dot(output_hidden_layer, weights[-1])
    output_signal = activation_function(p)
    
    return output_signal
def compute_gradient():
    # TODO
    pass
weights = init_weights(num_input_cells=2, num_output_cells=2, num_cell_per_hidden_layer=3, num_hidden_layers=1)
print(weights)
#print(weights[0].shape)
#print(weights[1].shape)
input_signal = np.array([.1, .2])
input_signal
evaluate_network(weights, input_signal)<jupyter_output><empty_output><jupyter_text>Divers Le PMC peut approximer n'importe quelle fonction **continue** avec une précision arbitraire suivant le nombre de neurones présents sur la couche cachée. Initialisation des poids: généralement des petites valeurs aléatoires<jupyter_code># TODO: la différence entre:
# * réseau bouclé
# * réseau récurent<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1956162876008804, 'fraction_numerical': 0.037279897285399856, 'mean_word_length': 4.733797817799395, 'pattern_counts': {'":': 0, '<': 87, '<?xml version=': 0, '>': 91, 'https://': 7, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '21', 'dir': 'jupyter-structured-clean-dedup', 'id': '605323', 'license': 'MIT', 'n_tokens_mistral': 18529, 'n_tokens_neox': 17975, 'n_words': 3828, 'path': 'nb_sci_ai/ai_ml_multilayer_perceptron_fr.ipynb', 'repo_name': 'jdhp-docs/python-notebooks', 'size': '43636'}