<jupyter_start><jupyter_text>$\newcommand{\xbf}{{\bf x}}\newcommand{\ybf}{{\bf y}}\newcommand{\wbf}{{\bf w}}\newcommand{\Ibf}{\mathbf{I}}\newcommand{\Xbf}{\mathbf{X}}\newcommand{\Rbb}{\mathbb{R}}\newcommand{\vec}[1]{\left[\begin{array}{c}1\end{array}\right]}$ Introduction aux réseaux de neurones : TD 1 (partie 2)Matériel de cours rédigé par <NAME>, 2018************<jupyter_code>%matplotlib inline
from matplotlib import pyplot as plt
import numpy as np
from math import sqrt
import aidecours<jupyter_output><empty_output><jupyter_text>Le module `torch.nn`<jupyter_code>import torch<jupyter_output><empty_output><jupyter_text>Le module `nn` de la librairie `torch` contient plusieurs outils pour construire l'architecture d'un réseau de neurone.<jupyter_code>from torch import nn<jupyter_output><empty_output><jupyter_text>Reprenons l'exemple des moindres carrés de la partie précédente, afin de montrer comment exprimer le problème sous la forme d'une réseau de neurones avec les outils qu'offrent *pyTorch*.  Préparation des donnéesPréparons les données d'apprentissage sous la forme de *tenseurs pyTorch*.<jupyter_code>x_tensor = torch.tensor([(1,1),(0,-1),(2,.5)])
y_tensor = torch.tensor([-1., 3, 2])
x_tensor
y_tensor
y_tensor = y_tensor.unsqueeze(1) # Les méthodes du module torch.nn sont conçues pour manipuler des matrices
y_tensor<jupyter_output><empty_output><jupyter_text>Couche linéaireLa classe `Linear` correspond à une *couche* linéaire. La méthode des moindres carrés nécessite seulement un neurone de sortie.<jupyter_code>nn.Linear?
neurone = nn.Linear(2, 1, bias=False)
neurone
neurone.weight
neurone(x_tensor)<jupyter_output><empty_output><jupyter_text>Fonction de perte<jupyter_code>nn.MSELoss?
perte_quadratique = nn.MSELoss()
perte_quadratique(neurone(x_tensor), y_tensor)<jupyter_output><empty_output><jupyter_text>Module d'optimisation `torch.optim`<jupyter_code>torch.optim.SGD?
eta = 0.4
alpha = 0.1

neurone = nn.Linear(2, 1, bias=False)
optimizer = torch.optim.SGD(neurone.parameters(), lr=eta, momentum=alpha)

for t in range(20):

    y_pred = neurone(x_tensor)
    loss = perte_quadratique(y_pred, y_tensor)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    print(t, loss.item())<jupyter_output><empty_output><jupyter_text>Remettons tout cela ensemble afin de réécrire le module `moindre_carres` avec les outils de *pyTorch*<jupyter_code>class moindre_carres:
    def __init__(self, eta=0.4, alpha=0.1, nb_iter=50, seed=None):
        self.eta=eta
        self.alpha=alpha
        self.nb_iter=nb_iter
        self.seed = seed
        
    def fit(self, x, y):
        if self.seed is not None:
            torch.manual_seed(self.seed)
        
        x = torch.tensor(x, dtype=torch.float32)
        y = torch.tensor(y, dtype=torch.float32).unsqueeze(1)

        n, d = x.shape
        self.neurone = nn.Linear(d, 1, bias=False)
        perte_quadratique = nn.MSELoss()
        optimizer = torch.optim.SGD(self.neurone.parameters(), lr=self.eta, momentum=self.alpha)
        
        self.w_list = list()
        self.obj_list = list()
    
        for t in range(self.nb_iter+1):
            y_pred = self.neurone(x)
            
            loss = perte_quadratique(y_pred, y)
            
            self.w_list.append(np.array(self.neurone.weight[0].detach()))
            self.obj_list.append(loss.item()) 
            
            if t < self.nb_iter:
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()
                
    def predict(self, x):
        x = torch.tensor(x, dtype=torch.float32)
        pred = self.model(x)
        return np.array(pred.detach())[:,0]
def moindre_carres_objectif(x, y, w): 
    return np.sum((x @ w - y) ** 2)

x = np.array([(1,1),(0,-1),(2,.5)])
y = np.array([-1., 3, 2])

fonction_objectif = lambda w: moindre_carres_objectif(x, y, w)
eta = 0.4      # taille du pas
alpha = 0.0    # momentum
nb_iter = 40       # nombre d'itérations

algo = moindre_carres(eta, alpha, nb_iter, seed=None)
algo.fit(x, y)
w_opt = np.linalg.inv(x.T @ x) @ x.T @ y
fig, axes = plt.subplots(1, 2, figsize=(14.5, 4))
aidecours.sgd_trajectoire(algo.w_list, fonction_objectif, ax=axes[0])
aidecours.sgd_courbe_objectif(algo.obj_list, ax=axes[1], obj_opt=fonction_objectif(w_opt))<jupyter_output><empty_output><jupyter_text>Ajout d'une couche cachée<jupyter_code>couche_cachee = nn.Linear(2, 4)
couche_cachee
couche_cachee.weight
couche_cachee.bias
for variables in couche_cachee.parameters():
    print(variables)
    print('---')
couche_cachee(x_tensor)<jupyter_output><empty_output><jupyter_text>Fonctions d'activationsFonction d'activation *ReLU*<jupyter_code>nn.ReLU?
activation_relu = nn.ReLU()
a = torch.linspace(-2, 2, 5)
a
activation_relu(a)
activation_relu(couche_cachee(x_tensor))<jupyter_output><empty_output><jupyter_text>Fonction d'activation *tanh*<jupyter_code>nn.Tanh?
activation_tanh = nn.Tanh()
activation_tanh(a)
activation_tanh(couche_cachee(x_tensor))<jupyter_output><empty_output><jupyter_text>Fonction d'activation *sigmoïdale*<jupyter_code>nn.Sigmoid?
activation_sigmoide = nn.Sigmoid()
activation_sigmoide(a)
activation_sigmoide(couche_cachee(x_tensor))<jupyter_output><empty_output><jupyter_text>Succession de couches et de fonctions d'activations<jupyter_code>nn.Sequential?
model = nn.Sequential(
    torch.nn.Linear(2, 4),
    torch.nn.ReLU(),
)
model(x_tensor)
model = nn.Sequential(
    torch.nn.Linear(2, 4),
    torch.nn.ReLU(),
    torch.nn.Linear(4, 1),
)
model(x_tensor)
for variables in model.parameters():
    print(variables)
    print('---')<jupyter_output><empty_output><jupyter_text>Réseau de neurones à une couche cachée<jupyter_code>class reseau_regression:
    def __init__(self, nb_neurones=4, eta=0.4, alpha=0.1, nb_iter=50, seed=None):
        self.nb_neurones = nb_neurones
        self.eta = eta
        self.alpha = alpha
        self.nb_iter = nb_iter
        self.seed = seed
        
    def fit(self, x, y):
        if self.seed is not None:
            torch.manual_seed(self.seed)
        
        x = torch.tensor(x, dtype=torch.float32)
        y = torch.tensor(y, dtype=torch.float32).unsqueeze(1)

        n, d = x.shape
        
        self.model = nn.Sequential(
            torch.nn.Linear(d, self.nb_neurones),
            torch.nn.ReLU(),
            torch.nn.Linear(self.nb_neurones, 1)
        )
        
        perte_quadratique = nn.MSELoss()
        optimizer = torch.optim.SGD(self.model.parameters(), lr=self.eta, momentum=self.alpha)
        
        self.obj_list = list()
    
        for t in range(self.nb_iter+1):
            y_pred = self.model(x)
            
            loss = perte_quadratique(y_pred, y)
                  
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            
            self.obj_list.append(loss.item())
                
    def predict(self, x):
        x = torch.tensor(x, dtype=torch.float32)
        pred = self.model(x)
        return np.array(pred.detach())[:,0]
nb_neurones = 4
eta = 0.1      # taille du pas
alpha = 0.1    # momentum
nb_iter = 50       # nombre d'itérations

x = np.array([(1,1),(0,-1),(2,.5)])
y = np.array([-1., 3, 2])

algo = reseau_regression(nb_neurones, eta, alpha, nb_iter, seed=None)
algo.fit(x, y)

aidecours.sgd_courbe_objectif(algo.obj_list)
predictions = algo.predict(x)
print('y    =', y)
print('R(x) =', predictions)<jupyter_output><empty_output><jupyter_text>Exercice L'objectif de cet exercice est d'adapter la classe `reseau_regression` présentée plus haut pour résoudre le problème de *classification* suivant.<jupyter_code>from sklearn.datasets import make_circles
xx, yy = make_circles(n_samples=100, noise=.1, factor=0.2, random_state=10)
aidecours.show_2d_dataset(xx, yy)<jupyter_output><empty_output><jupyter_text>Nous vous demandons de compléter la fonction `fit` de la classe `reseau_classification` ci-bas. Nous vous conseillons de vous inspirer de la régression logistique en utilisant une fonction d'activation *sigmoïdale* en sortie, ainsi que la perte du **négatif log vraisemblance**. Il n'est pas nécessaire d'ajouter un terme de régularisation au réseau.**Notez bien**: La fonction de perte du **négatif log vraisemblance** vue en classe correspond à la classe `nn.BCELoss`<jupyter_code>class reseau_classification:
    def __init__(self, nb_neurones=4, eta=0.4, alpha=0.1, nb_iter=50, seed=None):
        self.nb_neurones = nb_neurones
        self.eta = eta
        self.alpha = alpha
        self.nb_iter = nb_iter
        self.seed = seed
        
    def fit(self, x, y):
        if self.seed is not None:
            torch.manual_seed(self.seed)
        
        x = torch.tensor(x, dtype=torch.float32)
        y = torch.tensor(y, dtype=torch.float32).unsqueeze(1)

        n, d = x.shape
        
        self.model = nn.Sequential(
            torch.nn.Linear(d, self.nb_neurones),
            # Compléter l'architecture ici
        )
        
        perte_logistique = nn.BCELoss()
        optimizer = torch.optim.SGD(self.model.parameters(), lr=self.eta, momentum=self.alpha)
        
        self.obj_list = list()
    
        for t in range(self.nb_iter):
            pass # Compléter l'apprentissage ici
                
    def predict(self, x):
        x = torch.tensor(x, dtype=torch.float32)
        pred = self.model(x)
        return np.array(pred.detach() > .5, dtype=np.int)[:,0]<jupyter_output><empty_output><jupyter_text>Exécuter le code suivant pour tester votre réseau. Varier les paramètres pour mesurer leur influence.<jupyter_code>nb_neurones = 10
eta = 0.6     # taille du pas
alpha = 0.4    # momentum
nb_iter = 50  # nombre d'itérations

algo = reseau_classification(nb_neurones, eta, alpha, nb_iter)
algo.fit(xx, yy)

fig, axes = plt.subplots(1, 2, figsize=(12, 4))
aidecours.sgd_courbe_objectif(algo.obj_list, ax=axes[0])
aidecours.show_2d_predictions(xx, yy, algo.predict, ax=axes[1]);<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12473742122636791, 'fraction_numerical': 0.015404621386415926, 'mean_word_length': 3.4653863331844574, 'pattern_counts': {'":': 0, '<': 71, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '21217', 'license': 'CC-BY-4.0', 'n_tokens_mistral': 3783, 'n_tokens_neox': 3702, 'n_words': 716, 'path': 'travaux_diriges/TD1 - Intro a pyTorch - Partie 2.ipynb', 'repo_name': 'pgermain/cours2018-Intro_aux_r-seaux_de_neurones', 'size': '10005'}