Le théorème de Taylor illustré

exercice : niveau avancé

En guise d’application de ce qu’on a vu jusqu’ici, je vous invite à réaliser une visualisation du théorème de Taylor; je vous renvoie à votre cours d’analyse, ou à wikipedia pour une présentation détaillée de ce théorème, mais ce que nous en retenons se résume à ceci.

On peut approximer une fonction “suffisamment régulière” - disons $C^\infty$ pour fixer les idées - par un polynôme d’ordre $n$, dont les coefficients dépendent uniquement des $n$ dérivées successives au voisinage d’un point :

$$f_n(x) = \sum_{i=0}^{n}\frac{f^{(i)}(0).x^i}{i!}$$

Sans perte de généralité nous avons ici fixé le point de référence comme étant égal à 0, il suffit de translater $f$ par changement de variable pour se ramener à ce cas-là.

Le théorème de Taylor nous dit que la suite de fonctions $(f_n)$ converge vers $f$.

On pourrait penser - c’était mon cas la première fois que j’ai entendu parler de ce théorème - que l’approximation est valable au voisinage de 0 seulement; si on pense en particulier à sinus, on peut accepter l’idée que ça va nous donner une période autour de 0 peut-être.

En fait, c’est réellement bluffant de voir que ça marche vraiment incroyablement bien et loin.

mon implémentation

Je commence par vous montrer seulement le résultat de l’implémentation que j’ai faite.

Pour calculer les dérivées successives j’utilise la librairie autograd.

Ce code est relativement générique, vous pouvez visualiser l’approximation de Taylor avec une fonction que vous passez en paramètre - qui doit avoir tout de même la bonne propriété d’être vectorisée, et d’utiliser la couche numpy exposée par autograd :

# to compute derivatives
import autograd
import autograd.numpy as np

Sinon pour les autres dépendances, j’ai utilisé les ipywidgets et bokeh

from math import factorial

from ipywidgets import interact, IntSlider, Layout
from bokeh.plotting import figure, show
from bokeh.io import push_notebook, output_notebook

output_notebook()
Loading BokehJS ...

la classe Taylor

J’ai défini une classe Taylor, je ne vous montre pas encore le code, je vais vous inviter à en écrire une vous même; nous allons voir tout de suite comment l’utiliser, mais pour la voir fonctionner il vous faut l’évaluer :

↓↓↓↓↓ ↓↓↓↓↓ assurez-vous de bien évaluer la cellule cachée ici ↓↓↓↓↓ ↓↓↓↓↓

# @BEG@ name=taylor
class Taylor:
    """
    provides an animated view of Taylor approximation
    where one can change the degree interactively
        
    Taylor is applied on X=0, translate as needed
    """

    def __init__(self, function, domain):
        self.function = function
        self.domain = domain
        
    def display(self, y_range):
        """
        create initial drawing with degree=0
        
        Parameters:
          y_range: a (ymin, ymax) tuple
            for the animation to run smoothly, we need to display
            all Taylor degrees with a fixed y-axis range
        """
        # create figure
        x_range = (self.domain[0], self.domain[-1])
        self.figure = figure(title=self.function.__name__,
                             x_range=x_range, y_range=y_range)

        # each of the 2 curves is a bokeh line object
        self.figure.line(self.domain, self.function(self.domain), color='green')
        # store this in an attribute so _update can do its job
        self.line_approx = self.figure.line(
            self.domain, self._approximated(0), color='red', line_width=2)

        # needed so that push_notebook can do its job down the road
        self.handle = show(self.figure, notebook_handle=True)
# @END@

# @BEG@ name=taylor continued=true
    def _approximated(self, degree):
        """
        Computes and returns the Y array, the images of the domain
        through Taylor approximation
        
        Parameters:
          degree: the degree for Taylor approximation
        """
        # initialize with a constant f(0)
        # 0 * self.domain allows to create an array
        # with the right length
        result = 0 * self.domain + self.function(0.)
        # f'
        derivative = autograd.grad(self.function)
        for n in range(1, degree+1):
            # the term in f(n)(x)/n!
            result += derivative(0.)/factorial(n) * self.domain**n
            # next-order derivative
            derivative = autograd.grad(derivative)
        return result

    def _update(self, degree):
        # update the second curve only, of course
        # the 2 magic lines for bokeh updates
        self.line_approx.data_source.data['y'] = self._approximated(degree)
        push_notebook(handle=self.handle)
        
    def interact(self, degree_widget):
        """
        Parameters:
          degree_widget: a ipywidget, typically an IntSlider
            styled at your convenience
        """
        interact(lambda degree: self._update(degree), degree=degree_widget)
# @END@

↑↑↑↑↑ ↑↑↑↑↑ assurez-vous de bien évaluer la cellule cachée ici ↑↑↑↑↑ ↑↑↑↑↑

# check the code was properly loaded
help(Taylor)
Help on class Taylor in module __main__:

class Taylor(builtins.object)
 |  Taylor(function, domain)
 |  
 |  provides an animated view of Taylor approximation
 |  where one can change the degree interactively
 |      
 |  Taylor is applied on X=0, translate as needed
 |  
 |  Methods defined here:
 |  
 |  __init__(self, function, domain)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  display(self, y_range)
 |      create initial drawing with degree=0
 |      
 |      Parameters:
 |        y_range: a (ymin, ymax) tuple
 |          for the animation to run smoothly, we need to display
 |          all Taylor degrees with a fixed y-axis range
 |  
 |  interact(self, degree_widget)
 |      Parameters:
 |        degree_widget: a ipywidget, typically an IntSlider
 |          styled at your convenience
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

sinus

Ma classe Taylor s’utilise comme ceci : d’abord on crée une instance à partir d’une fonction et d’un domaine, i.e. l’intervalle des X qui nous intéresse.

# between -4π and 4π
DOMAIN = np.linspace(-4*np.pi, 4*np.pi, 250)

# an instance
sinus_animator = Taylor(np.sin, DOMAIN)

Remarquez bien qu’ici la fonction que je passe au constructeur est en réalité autograd.numpy.sin et non pas numpy.sin, vu la façon dont on a défini notre symbole np lors des imports (et ça ne marcherait pas du tout avec numpy.sin).

Ensuite on crée un ipywidget qui va nous permettre de choisir le degré $n$; dans le cas de sinus, qui est impaire, les degrés intéressants sont impairs (vous pouvez vérifier que les coefficients de Taylor pairs sont nuls lorsque $f$ est impaire).

# the widget to select a degree
sinus_widget = IntSlider(
   min=1, max=33, step=2,        # sinus being odd we skip even degrees
   layout=Layout(width='100%'))  # more convenient with the whole page width

Pour lancer l’interaction, on n’a plus qu’à :

  • afficher le diagramme avec la méthode display(); on a besoin pour cela de préciser les bornes en Y, qui resteront constantes au cours de l’animation (sinon la visualisation est vilaine)

puis lancer l’interaction en passant en paramètre le widget qui choisit le degré, ce qui donne :

# fixed limits in Y
sinus_animator.display((-1.5, 1.5))

sinus_animator.interact(sinus_widget)

cosinus

La même chose avec cosinus nous donnerait ceci :

# allows to select a degree
sinus_widget = IntSlider(
   min=0, max=34, step=2,      # only even degrees
   layout=Layout(width='100%'))

### ready to go
sinus_animator = Taylor(np.cos, DOMAIN)
sinus_animator.display((-1.5, 1.5))
sinus_animator.interact(sinus_widget)

exponentielle

# allows to select a degree
exp_widget = IntSlider(min=0, max=17,
   layout=Layout(width='100%'))

### ready to go
exp_animator = Taylor(np.exp, np.linspace(-5, 10, 200))
exp_animator.display((-15_000, 25000))
exp_animator.interact(exp_widget)

quelques indices

affichage

Ici j’ai utilisé bokeh, mais on peut tout à fait arriver à quelque chose de similaire avec matplotlib sans aucun doute

conception

Ma classe Taylor s’inspire très exactement de la technique décrite dans le Complément #6 “Autres bibliothèques de visualisation”, et notamment la classe Animation, modulo quelques renommages.

calcul de dérivées avec autograd

La seule fonction que j’ai utilisée de la bibliothèque autograd est grad :

from autograd import grad
# dans le cas de sinus par exemple
# les dérivées successives en 0 se retrouvent comme ceci
f = np.sin  # à nouveau cette fonction est autograd.numpy.sin
f(0.)
0.0
# ordre 1
f1 = grad(f)
f1(0.)
1.0
# ordre 2
f2 = grad(f1)
f2(0.)
-0.0

votre implémentation

Je vous invite à écrire votre propre implémentation, qui se comporte en gros comme notre classe Taylor.

Vous pouvez naturellement simplifier autant que vous le souhaitez, ou modifier la signature comme vous le sentez (pensez alors à modifier aussi la cellule de test).

À titre indicatif ma classe Taylor fait environ 30 lignes de code utile, i.e. sans compter les lignes blanches, les docstrings et les commentaires.

# à vous de jouer

class MyTaylor:
    def __init__(self, function, domain):
        ...
    def display(self, y_range):
        # là on veut créer le dessin original, c'est à dire
        # la figure, la courbe de f qui ne chagera plus,
        # et la courbe approchée avec disons n=0 (donc y=f(0))
        ...
    def _update(self, n):
        # modifier la courbe approximative avec Taylor à l'ordre n
        # je vous recommande de créer cette méthode privée
        # pour pouvoir l'appeler dans interact()
        ...
    def interact(self, widget):
        # là clairement il va y avoir un appel à 
        # interact() de ipywidgets
        print("inactive for now")
# testing MyTaylor on cosinus

sinus_widget = IntSlider(
   min=0, max=34, step=2,      # only even degrees
   layout=Layout(width='100%'))

### ready to go
sinus_animator = MyTaylor(np.cos, DOMAIN)
sinus_animator.display((-1.5, 1.5))
sinus_animator.interact(sinus_widget)
inactive for now