Commit a15a63e8 authored by Matthieu Boileau's avatar Matthieu Boileau
Browse files

backpropagation: animation

parent 3d04c714
.venv/
*.swp
__pycache__/
.ipynb_checkpoints/
.DS_Store
nn.key
\ No newline at end of file
# Rétropropagation du gradient dans les réseaux de neurones à propagation avant
## Installation
Prérequis : python >= 3.6
```bash
python3 -m pip install -r requirements.txt
```
## Exécution du cours sous forme de notebooks jupyter
Lancer un serveur Jupyter local :
```bash
jupyter-notebook backpropagation.ipynb
```
Tester en ligne : [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/git/https%3A%2F%2Fgitlab.math.unistra.fr%2Fml%2Fslides_exposes/master?filepath=210310_backpropagation%2Fbackpropagation.ipynb)
## Utilisation du programme python
```
~$ python3 backpropation.py -h
NAME
backpropagation.py - Entraînement d'un réseau perceptron à deux couches sur un problème de classification
SYNOPSIS
backpropagation.py <flags>
DESCRIPTION
Entraînement d'un réseau perceptron à deux couches sur un problème de classification
FLAGS
--Pattern=PATTERN
Default: <class '__main__.Diamond'>
--n_obs=N_OBS
Default: 500
--Activation=ACTIVATION
Default: <class '__main__.Sigmoid'>
--learning_rate=LEARNING_RATE
Default: 0.001
--seed=SEED
Default: 1241
--n_iter=N_ITER
Default: 5000
--n_iter_out=N_ITER_OUT
Default: 50
--animate=ANIMATE
Default: True
--save=SAVE
Default: False
--verbose=VERBOSE
Default: False
```
On peut exporter l'animation en une suite d'images png dans le répertoire `png/` :
```
~$ python3 backpropation.py --save
```
Puis créer un gif animé de l'entrainement avec ImageMagick :
```bash
convert png/ite*.png training.gif
```
![animation d'apprentissage](training.gif)
This diff is collapsed.
"""
Entraînement d'un réseau perceptron à deux couches
sur un problème de classification
"""
import numpy as np
from matplotlib import pyplot as plt
class Diamond:
def __init__(self, n_obs=500):
self.n_obs = n_obs
x_0 = np.random.uniform(-1, 1, size=(n_obs, 2))
# a trick to use a weight component as a bias
x_bias = np.ones((n_obs, 1))
self.x = np.concatenate((x_0, x_bias), axis=1)
self.t = self.get_target()
def get_target(self):
return ((np.abs(self.x[:, 0]) + np.abs(self.x[:, 1])) < 1).astype(int)
def plot_boundary(self, ax):
ax.plot([-1, 0, 1, 0, -1], [0, 1, 0, -1, 0], 'grey', linestyle='solid')
def plot(self):
fig, ax = plt.subplots(figsize=(6, 6))
ax.plot(self.x[self.t == 1, 0],
self.x[self.t == 1, 1], 'o', label='classe 1')
ax.plot(self.x[self.t == 0, 0], self.x[self.t == 0, 1],
'o', mfc='none', label='classe 0')
self.plot_boundary(ax)
ax.legend()
ax.axis('equal')
class Sigmoid:
@staticmethod
def function(x):
return 1 / (1 + np.exp(-x))
def derivative(self, x):
return self.function(x)*(1 - self.function(x))
def plot(self, ax=None):
x = np.linspace(-5, 5, 100)
if ax is None:
fig, ax = plt.subplots(figsize=(5, 3))
ax.plot(x, self.function(x), label='$g$')
ax.plot(x, self.derivative(x), linestyle='dashed', label="$g'$")
ax.set_xlabel('$x$')
ax.set_title(self.__class__.__name__)
ax.legend()
class ReLU(Sigmoid):
@staticmethod
def function(x):
return np.maximum(x, 0)
def derivative(self, x):
return np.heaviside(x, 0)
class Tanh(Sigmoid):
@staticmethod
def function(x):
return np.tanh(x)
def derivative(self, x):
return 1 - self.function(x)*self.function(x)
def log_loss(t, y, eps=1e-16):
y = np.maximum(y, eps)
y = np.minimum(y, 1 - eps)
return - (np.sum(t * np.log(y)) + np.sum((1 - t) * np.log(1 - y))) / t.size
def binary_log_loss(t, y):
return - (t * np.log(y) + (1 - t) * np.log(1 - y))
class Network:
def __init__(self, pattern, Activation=Sigmoid, learning_rate=0.001, seed=1241):
self.pattern = pattern
self.x = self.pattern.x # input
self.t = self.pattern.t # output target
self.g = Activation()
self.learning_rate = learning_rate
self.n_obs = self.x.shape[0]
# Initialize weights with uniform random values in [-1, 1]
np.random.seed(seed)
self.w1 = np.random.uniform(-1, 1, size=(3, 4))
self.w2 = np.random.uniform(-1, 1, size=(4, ))
self.pred_subplot = None
self.anim_fig = None
self.iter = 0
def __repr__(self):
return f"""\
Training on {self.n_obs} observations:
Activation function: {self.g.__class__.__name__}
learning_rate = {self.learning_rate}
"""
def forward_backward(self):
"""Return the new prediction y and gradients"""
# Forward propagation
h1 = self.x @ self.w1
x1 = self.g.function(h1)
h2 = x1 @ self.w2
y = self.g.function(h2)
# Backward propagation
delta2 = y - self.t # only valid when log loss and Sigmoid are used!
dE_dw2 = delta2 @ x1
dh1_dx1 = self.g.derivative(h1)
delta1 = ((delta2.reshape(-1, 1) @ self.w2.reshape(-1, 1).T) * dh1_dx1).T
dE_dw1 = (delta1 @ self.x).T
return y, dE_dw1, dE_dw2
def update_plot(self):
fig.canvas.draw()
fig.canvas.flush_events()
def train(self, n_iter=5000, verbose=True, animate=False, save=True,
n_iter_out=200):
# Define new quantities for output
self.losses = np.empty((n_iter, ), dtype=float)
self.accuracies = np.empty_like(self.losses)
self.w1s = np.empty((n_iter, 3 * 4), dtype=float)
self.w2s = np.empty((n_iter, 4), dtype=float)
if verbose:
print(self)
subplot = None
for self.iter in range(n_iter):
self.y, dE_dw1, dE_dw2 = self.forward_backward()
# Update the weight matrices
self.w1 -= self.learning_rate * dE_dw1
self.w2 -= self.learning_rate * dE_dw2
# Store the weights for output
self.w1s[self.iter] = self.w1.reshape(3 * 4)
self.w2s[self.iter] = self.w2
# Compute the loss and accuracy
loss = log_loss(self.t, self.y)
accuracy = np.sum((self.y >= 0.5) == self.t) / self.n_obs
self.losses[self.iter] = loss
self.accuracies[self.iter] = accuracy
if (self.iter + 1) % n_iter_out == 0:
if verbose:
print(
f'ite {i:5d}, loss = {loss:.4f}, accuracy = {accuracy}'
)
if animate:
# suptitle = (
# f"Entraînement sur {self.n_obs} observations "
# f"avec la fonction {self.g.__class__.__name__} "
# f"et $\lambda = {self.learning_rate}$")
# self.plot_predictions(suptitle=suptitle,
# title=f"ite = {i + 1:05}"
# )
self.plot_animation(n_iter, save=save)
print(f'ite {self.iter:5d}, loss = {loss:.4f}, accuracy = {accuracy}')
if animate:
plt.show()
def plot_loss(self, ax):
ax.plot(self.losses[:self.iter])
ax.grid(True)
ax.set(xlabel='itérations', title='Fonction erreur')
def plot_accuracy(self, ax):
ax.plot(self.accuracies[:self.iter])
ax.grid(True)
ax.set(xlabel='itérations', title='Précision')
def plot_loss_accuracy(self):
fig = plt.figure(figsize=(14, 6))
fig.suptitle("Erreur et précision en fonction de l'itération")
self.plot_loss(fig.add_subplot(1, 2, 1))
self.plot_accuracy(fig.add_subplot(1, 2, 2))
def plot_w1(self, ax):
labels = [f"$w^{{(1)}}_{{{i + 1}{j + 1}}}$"
for (i, j), _ in np.ndenumerate(self.w1)]
ax.plot(self.w1s[:self.iter])
ax.grid(True)
ax.set(xlabel='itérations', title='$w^{{(1)}}$')
ax.legend(labels)
def plot_w2(self, ax):
labels = [f"$w^{{(2)}}_{i + 1}$"
for (i, ), _ in np.ndenumerate(self.w2)]
ax.plot(self.w2s[:self.iter])
ax.grid(True)
ax.set(xlabel='itérations', title='$w^{{(2)}}$')
ax.legend(labels)
def plot_weights(self):
fig = plt.figure(figsize=(14, 6))
fig.suptitle("Poids en fonction de l'itération")
self.plot_w1(fig.add_subplot(1, 2, 1))
self.plot_w2(fig.add_subplot(1, 2, 2))
def plot_pred(self, ax, title):
pred1 = (self.y >= 0.5)
pred0 = (self.y < 0.5)
# True predictions
ax.plot(self.x[pred1 & (self.t == 1), 0],
self.x[pred1 & (self.t == 1), 1],
'o', label='Vrais positifs')
ax.plot(self.x[pred0 & (self.t == 0), 0],
self.x[pred0 & (self.t == 0), 1],
'o', mfc='none', label='Vrais négatifs')
self.pattern.plot_boundary(ax)
# False predictions
ax.plot(self.x[pred1 & (self.t == 0), 0],
self.x[pred1 & (self.t == 0), 1],
'+', label='Faux positifs', markersize=15)
ax.plot(self.x[pred0 & (self.t == 1), 0],
self.x[pred0 & (self.t == 1), 1],
'+', mfc='none', label='Faux négatifs', markersize=15)
ax.set_title(title)
ax.legend(loc='upper right')
ax.axis('equal')
def plot_predictions(self, title='', suptitle=''):
if self.pred_subplot is None:
self.pred_subplot = plt.subplots(figsize=(8, 8))
fig, ax = self.pred_subplot
fig.suptitle('')
ax.clear()
self.plot_pred(ax, title)
fig.canvas.draw()
fig.canvas.flush_events()
plt.pause(0.01)
fig.savefig(f"png/{title.replace(' ', '')}.png")
def plot_animation(self, n_iter, save=False):
if self.anim_fig is None:
self.anim_fig = plt.figure(figsize=(14, 9))
fig = self.anim_fig
ite = f"{self.iter + 1:05}"
fig.suptitle(
f"Entraînement sur {self.n_obs} observations "
f"avec la fonction {self.g.__class__.__name__} "
f"et $\lambda = {self.learning_rate}$ "
f"\nite = {ite}"
)
for ax in fig.get_axes():
ax.remove()
ax1 = fig.add_subplot(2, 3, 1)
ax1.set_xlim(0, n_iter)
self.plot_loss(ax1)
ax2 = fig.add_subplot(2, 3, 2, sharex=ax1)
self.plot_accuracy(ax2)
self.plot_pred(fig.add_subplot(2, 3, 3), title='Prédictions')
ax4 = fig.add_subplot(2, 3, 4, sharex=ax1)
self.plot_w1(ax4)
ax5 = fig.add_subplot(2, 3, 5, sharex=ax1)
self.plot_w2(ax5)
plt.subplots_adjust(hspace=0.3)
fig.canvas.draw()
fig.canvas.flush_events()
plt.pause(0.01)
if save:
fig.savefig(f"png/ite_{ite}.png", dpi=100)
class Square(Diamond):
def get_target(self):
return ((np.maximum(np.abs(self.x[:, 0]), np.abs(self.x[:, 1]))) < 0.5).astype(int)
def plot_boundary(self, ax):
ax.plot([-0.5, 0.5, 0.5, -0.5, -0.5], [0.5, 0.5, -0.5, -0.5, 0.5],
'grey', linestyle='solid')
class Circle(Diamond):
def get_target(self):
return (np.sqrt(self.x[:, 0]**2 + self.x[:, 1]**2) < 0.75).astype(int)
def plot_boundary(self, ax):
circle = plt.Circle((0, 0), 0.75, color='grey', fill=False)
ax.add_patch(circle)
def main(Pattern=Diamond, n_obs=500, Activation=Sigmoid, learning_rate=0.001,
seed=1241, n_iter=5000, n_iter_out=50, animate=True, save=False,
verbose=False):
pattern = Pattern(n_obs=n_obs)
net = Network(pattern=pattern, Activation=Activation,
learning_rate=learning_rate, seed=seed)
net.train(n_iter=n_iter, animate=animate, verbose=verbose,
n_iter_out=n_iter_out, save=save)
plt.show()
return net
if __name__ == '__main__':
import fire
# so fire can use module docstring as CLI description:
main.__doc__ = __doc__
fire.Fire(main)
# Ignore everything in this directory
*
# Except this file
!.gitignore
matplotlib
jupyter
jupyter_contrib_nbextensions
rise
fire
\ No newline at end of file
import backpropagation as bp
from ipywidgets import (widgets, interactive_output, IntSlider,
FloatSlider, RadioButtons, Layout, Box, VBox)
def plot_net(Pattern, Activation, n_obs, n_iter, learning_rate, seed):
net = bp.Network(Pattern(n_obs),
Activation=Activation,
learning_rate=learning_rate,
seed=seed)
print("Entraînement du réseau...")
net.train(n_iter=n_iter, verbose=False)
net.plot_animation(net.iter)
bp.plt.show()
patterns = ['Diamond', 'Circle', 'Square']
pattern_classes = bp.Diamond, bp.Circle, bp.Square
pattern_buttons = widgets.RadioButtons(
options=list(zip(patterns, pattern_classes)),
description='Pattern:',
disabled=False
)
activation_functions = ['Sigmoid', 'ReLU', 'tanh']
activation_function_classes = bp.Sigmoid, bp.ReLU, bp.Tanh
activation_functions_buttons = widgets.RadioButtons(
options=list(zip(activation_functions, activation_function_classes)),
description='Activ. func.:',
disabled=False
)
n_obs_slider = IntSlider(min=100, max=10000, step=100, value=500,
continuous_update=False,
description=r'\(n_{obs}:\)')
n_iter_slider = IntSlider(min=100, max=50000, step=100, value=5000,
continuous_update=False,
description=r'\(n_{iter}:\)')
learning_rate_text = widgets.BoundedFloatText(
value=0.001, min=0., max=1., step=0.0001,
description=r'\(\lambda :\)',
disabled=False
)
seed_text = widgets.BoundedIntText(
value=1241, min=0, max=10000, step=1,
description='Graine',
disabled=False
)
kw = dict(Pattern=pattern_buttons,
Activation=activation_functions_buttons,
n_obs=n_obs_slider,
n_iter=n_iter_slider,
learning_rate=learning_rate_text,
seed=seed_text)
out = interactive_output(plot_net, kw)
box_layout = Layout(display='flex',
flex_flow='row',
align_items='stretch',
width='100%')
box_1 = Box(children=[pattern_buttons,
activation_functions_buttons, seed_text],
layout=box_layout)
box_2 = Box(children=[n_obs_slider, n_iter_slider,
learning_rate_text],
layout=box_layout)
ui = VBox([box_1, box_2])
display(ui, out)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment