La composition des équipes est fournie avec cet énoncé et est obligatoire. [cliquez ici pour voir]
Dans ce projet, vous devez monter différents réseaux de neurones pour différentes tâches de classification et de régression.
Ce projet sert d'introduction aux réseaux de neurones profonds.
Le code pour ce projet contient les fichiers ci-dessous, disponibles dans un fichier compressé.
Fichiers à modifier : | |
models.py |
Contient les réseaux de neurones pour les différentes applications |
Fichiers à lire seulement (NE PAS modifier) : | |
nn.py |
Mini-librairie de réseau de neurones |
Autres fichiers : | |
autograder.py |
Auto-correcteur du projet |
backend.py |
Backend pour différentes tâches |
data |
Contient les différentes données d'entraînement |
Fichiers à modifier et soumettre : vous devez remplir les sections manquantes du fichier
models.py
. Il faut ne faut pas modifier les autres fichiers.
Évaluation : l'auto-correcteur s'assure du bon fonctionnement de votre code. Ne changez aucun nom de fonction ou nom de classe dans le code, sans quoi l'auto-correcteur ne fonctionnera pas. L'auto-correcteur ne détermine pas entièrement votre résultat final. La qualité de votre implémentation - et non les résultats obtenus par l'auto-correcteur - déterminent votre résultat final.
Utilisation des données : une partie des notes obtenues dépend de la performance de votre modèle sur l'ensemble de test. La base de code n'offre aucun API permettant d'accéder à cet ensemble directement. Par conséquent, toute tentative de modification des données de test sera considérée comme de la tricherie et sera sévèrement pénalisée en conséquence.
Aide : N'hésitez pas à contacter les assistants à l'enseignement pour ce cours afin de vous aider dans le travail.
Pour ce projet, vous devez installer les deux librairies suivantes :
Note : Vous pouvez réutiliser l'environnement du TP1 dans lequel vous avez déjà installé ces dépendances. Il vous manquera seulement l'installation de PyTorch.
On rappelle ci-dessous comment installer des librairies à l'aide de conda et pip :
conda activate [le nom de votre environnement]
pip install numpy
pip install matplotlib
pip install torch
Contrairement au TP1, vous ne devriez pas utiliser ces librairies directement (a l'exception de PyTorch pour la question 4). Elles sont toutefois nécessaires au fonctionnement de l'auto-correcteur et du backend.
Pour vérifier l'installation des dépendances, exécutez la commande suivante :
python autograder.py --check-dependencies
Vous devriez alors observer une fenêtre s'ouvrir avec un ligne qui tourne dans un cercle
Pour ce projet, vous avez accès à une mini-librairie de réseau de neurones (nn.py
) ainsi qu'une collection de base de données (backend.py
).
La librairie nn.py
définie une collection d'objets node. Chaque node
représente un nombre réel ou une matrice de nombres réels. Les opérations sur les objets node sont optimisées
et donc plus rapides
que les types primitifs de Python (comme les listes par exemple).
Lorsqu'on entraîne un réseau de neurones, on met à jour les paramètres après avoir effectué des prédictions sur plusieurs données contrairement au perceptron du TP1 où les poids étaient affectés après chaque prédiction. Cette procédure d'entraînement par lot permet ainsi au réseau de mieux se généraliser et de ne pas se sur-spécialiser sur chacune des observations singulières. L'argument $\text{batch_size}$ réfère au nombre d'observations utilisées pour mettre à jour le réseau. Aussi, veuillez noter que le paramètre $\text{num_features}$ dénote le nombre d'attribut ou de variables associées à chaque donnée.
Voici une brève description de l'API :
nn.Constant
représente une matrice de nombres réels
utilisée pour représenter les données en entrée, la sortie des modèles d'apprentissage, ou les étiquettes.
nn.Parameter
représente les paramètres qu'on optimise (par exemple,
la matrice W dans le perceptron multi-classe du TP1).
nn.DotProduct
calcule le produit scalaire.
nn.as_scalar
peut extraire un nombre à virgule flotante d'un node.
nn.Add
effectue une addition de matrice.
nn.Add(x, y)
accepte deux node de dimension
$\text{batch_size} \times \text{num_features}$ et retourne un node de dimension
$\text{batch_size} \times \text{num_features}$.nn.AddBias
ajoute un vecteur de biais.
nn.AddBias(features, bias)
accepte une matrice
features
de dimension \(\text{batch_size} \times
\text{num_features}\)
et un biais bias
de dimension \(1 \times \text{num_features}\).
Retourne un node de dimension \(\text{batch_size} \times \text{num_features}\).nn.Linear
applique une transformation linéaire (multiplication de matrice) sur les arguments en entrée.
nn.Linear(features, weights)
accepte features
de dimension \(\text{batch_size} \times
\text{num_input_features}\) et weights
de dimension
\(\text{num_input_features} \times \text{num_output_features}\), et retourne un node de dimension
\(\text{batch_size} \times \text{num_output_features}\).nn.ReLU
applique la fonction Rectified Linear Unit
\(relu(x) = \max(x, 0)\) sur chaque élément de l'argument en entrée.
nn.ReLU(features)
, retourne un node avec la même
dimension
que l'argument en entrée.nn.SquareLoss
calcule une perte quadratique sur un batch (utilisée
pour la régression).
nn.SquareLoss(a, b)
, où a
et b
ont comme dimension
\(\text{batch_size} \times \text{num_outputs}\).nn.SoftmaxLoss
calcule la fonction softmax sur un batch (utilisée
pour les problèmes de classification).
nn.SoftmaxLoss(logits, labels)
, où logits
et labels
ont comme
dimension
\(\text{batch_size} \times \text{num_classes}\). Le terme “logits” refère aux scores produits par un
modèle.
Les étiquettes "labels" doivent être non-négatives.
nn.gradients
calcule le gradient d'une perte par rapport aux
paramètres passés comme arguments.
nn.gradients(loss, [parameter_1, parameter_2, ..., parameter_n])
retourne une liste [gradient_1, gradient_2, ..., gradient_n]
, où
chaque élément est un nn.Constant
contenant le gradient de la
perte par rapport à un paramètre.
nn.as_scalar
converti un node en type primitif Python pouvant s'avérer utile comme
critère d'arrêt lors de l'apprentissage.
nn.as_scalar(node)
, où node
est soit un node de perte ou possède la forme (1,1)
.
Additionnellement, les instances de datasets
possèdent les deux
méthodes suivantes :
dataset.iterate_forever(batch_size)
génère une suite infinie de batch
pour un exemple.
dataset.get_validation_accuracy()
retourne la métrique de justesse
("accuracy") de votre modèle sur l'ensemble de validation. Cette métrique peut aussi être utile comme critère d'arrêt lors de l'apprentissage.
À titre d'exemple, essayons de trouver les paramètres d'une équation linéaire permettant de prédire les valeurs de \(\mathbf{Y}\). On commence par \(|\mathbf{Y}| = 4\). L'équation optimale obtenue devrait être \( y = 7x_0 + 8x_1 + 3 \) (pour vous en convaincre, vous pouvez effectuer les calculs et remarquer la différence entre la prédiction et la valeur réelle). Autrement dit, à partir d'une matrice de données \(\mathbf{X}\), on essaie de trouver les paramètres \(m_0\), \(m_1\) et \(b\) qui permettent de prédire fidèlement chaque \(y \in \mathbf{Y}\).
and
Supposons que les données sont fournies en node nn.Constant
:
>>> x
<Constant shape=4x2 at 0x10a30fe80>
>>> y
<Constant shape=4x1 at 0x10a30fef0>
Nous entraînons un modèle de la forme \( f(x) = x_0 \cdot m_0 + x_1 \cdot m_1 +b \).
D'abord, on crée nos paramètres optimisables. Sous forme de matrice, ils sont de la forme suivante:
et
Ces formules se traduisent en code comme suit :
m = nn.Parameter(2, 1)
b = nn.Parameter(1, 1)
Leur interprétation en chaîne de caractères devrait donner :
>>> m
<Parameter shape=2x1 at 0x112b8b208>
>>> b
<Parameter shape=1x1 at 0x112b8beb8>
Ensuite, on calcule une prédiction de notre modèle pour un y :
xm = nn.Linear(x, m)
y_hat = nn.AddBias(xm, b)
Le but est que notre prédiction soit exacte, c'est-à-dire \(\hat{y} = y\). En régression linéaire, une telle tâche est réalisée en minimisant la somme des différences au carré : \( \mathcal{L} = \frac{1}{2N} \sum_{(x, y)} (y - f(x))^2 \).
On construit donc un node de type loss :
loss = nn.SquareLoss(y_hat, y)
La base de code permet le gradient de la perte en fonction des paramètres :
grad_wrt_m, grad_wrt_b = nn.gradients(loss, [m, b])
L'impression des gradients devrait donner :
>>> xm
<Linear shape=4x1 at 0x11a869588>
>>> y_hat
<AddBias shape=4x1 at 0x11c23aa90>
>>> loss
<SquareLoss shape=() at 0x11c23a240>
>>> grad_wrt_m
<Constant shape=2x1 at 0x11a8cb160>
>>> grad_wrt_b
<Constant shape=1x1 at 0x11a8cb588>
On utilise la méthode update
afin de mettre en jour les paramètres.
Voici un exemple de mise à jour de m
(on assume qu'une variable multiplier
a été initialisée avec un taux d'apprentissage adéquat) :
m.update(grad_wrt_m, multiplier)
Si on inclut la mise à jour de b
et qu'on répète les opérations
précédentes
dans une structure itérative, on retrouve une procédure complète de régression linéaire.
Lors de l'entraînement du réseau, vous recevez une instance de dataset
pour laquelle vous retrouvez les batch d'entraînement en appelant dataset.iterate_once(batch_size)
:
for x, y in dataset.iterate_once(batch_size):
...
L'exemple suivant extrait un batch size de 1 (c-à-d, un seul exemple d'entraînement) :
>>> batch_size = 1
>>> for x, y in dataset.iterate_once(batch_size):
... print(x)
... print(y)
... break
...
<Constant shape=1x3 at 0x11a8856a0>
<Constant shape=1x1 at 0x11a89efd0>
Les données d'entrées x
et les étiquettes associées y
sont données sur la forme de node de type nn.Constant
. Le format de x
est (batch_size, num_features)
, et le format de y
est (batch_size, num_outputs)
.
Voici un exemple de produit scalaire de x
avec lui-même représenté
d'abord en tant que node et ensuite en tant que type primitif Python.
>>> nn.DotProduct(x, x)
<DotProduct shape=1x1 at 0x11a89edd8>
>>> nn.as_scalar(nn.DotProduct(x, x))
1.9756581717465536
Dans le projet, vous devrez implémenter les modèles suivants :
Votre implémentation du réseau de neurones passe par nn.py
.
Comme nous avons vu dans le cours, un réseau de neurones de base possède une ou plusieurs couches qui effectuent toutes une opération linéaire (comme le
perceptron).
Les couches sont séparées par une fonction d'activation non-linéaire qui permet au réseau de modéliser des fonctions complexes.
Nous utiliserons la fonction ReLU comme fonction d'activation. Celle-ci est définie comme \(relu(x) = \max(x,
0)\). Par exemple, la sortie \(\mathbf{f}(\mathbf{x})\) d'un réseau simple à deux couches
pour une donnée \(\mathbf{x}\) est donnée par:
\[\mathbf{f}(\mathbf{x}) = relu(\mathbf{x} \cdot \mathbf{W_1} + \mathbf{b_1}) \cdot \mathbf{W_2} +
\mathbf{b}_2 \]
Ce réseau optimise les paramètres \(\mathbf{W_1}\), \(\mathbf{W_2}\), \(\mathbf{b}_1\)
et \(\mathbf{b}_2\) par la descente de gradient stochastique. \(\mathbf{W_1}\) est une matrice de format \(i \times h\),
où \(i\) et \(h\) correspondent respectivement au nombre de lignes du vecteur \(\mathbf{x}\) et la taille
de la couche cachée.
\(\mathbf{b_1}\) est un vecteur de dimension \(h\). Le choix de \(h\) est laissé à la discrétion du programmeur
ou de la programmeuse. Vous devez toutefois vous assurer que les dimensions entre les couches soient
adéquates sans quoi le produit scalaire ne fonctionnera pas ou retournera une matrice de dimension absurde.
L'utilisation d'un grand \(h\) permet généralement un plus grand pouvoir représentational du modèle,
mais ajoute plus de paramètres à optimiser en plus de dégénérer vers un modèle qui se généralise mal sur des
données nouvelles (sur-apprentissage).
On peut créer des réseaux plus profonds en ajoutant des couches. Par exemple, pour un réseau à trois couches, on a : \[ \mathbf{f}(\mathbf{x}) = relu(relu(\mathbf{x} \cdot \mathbf{W_1} + \mathbf{b_1}) \cdot \mathbf{W_2} + \mathbf{b}_2) \cdot \mathbf{W_3} + \mathbf{b_3} \]
Contrairement au perceptron, où vous optimisiez les paramètres après chaque exemple d'entraînement, vous devez maintenant optimiser le réseau en batch, c'est-à-dire en prenant plusieurs exemples d'entraînement au-lieu d'un seul. Formellement, au-lieu de mettre à jour les poids après chaque entrée \(x\) de taille D, vous devez considérer un ensemble de N d'entrées représenté comme une matrice \(X\) de dimension \(N \times D\).
Au premier TP, nous initialisions les paramètres par des valeurs nulles, ce qui est généralement déconseillé. En pratique, les paramètres du réseau de neurones sont initialisés pseudo-aléatoirement. En conséquent, vous pouvez être malchanceux et échouer certaines tâches de classification avec une très bonne architecture (problème des minima locaux). Cependant, ce résultat est plutôt rare dans notre contexte. Donc, une série de résultats négatifs avec l'auto-correcteur devrait être un indicateur que votre modèle n'est pas bien implémenté ou que vous devriez explorer d'autres architectures.
Voici quelques astuces pour vous aider dans l'élaboration des réseaux de neurones :
Valeurs recommandées pour les hyper-paramètres :
Pour cette question, vous entrainerez un réseau de neurones afin d'approximer la fonction \(\frac{x\text{cos}(x)}{4}\) sur l'intervalle \([-2\pi,2\pi]\)
Vous devrez compléter l'implémentation de la classe RegressionModel
dans
models.py
. Pour ce problème, une architecture relativement simple devrait suffir
(voir Astuces pour les réseaux de neurones). Utilisez nn.SquareLoss
comme perte.
Tâches :
RegressionModel.__init__
avec toute initialization nécessaire;RegressionModel.run
pour renvoyer un noeud $\text{batch_size} \times 1$ qui
représente les predictions de votre modèle;RegressionModel.get_loss
qui retourne la perte pour les entrées et les cibles données;RegressionModel.train
, qui doit entrainer votre modèle en utilisant des
mise-à-jours basées sur les gradients.Il n'y a qu'un seul jeu de données pour cette tâche (pas de validation ni de test car les données peuvent etre générées à la volée).
Votre implémentation doit obtenir une perte inférieure ou égale à 0.02 pour avoir tous les points. Vous pouvez utiliser la perte sur
l'ensemble d'entrainement pour déterminer quand vous arrêter (utilisez nn.as_scalar
pour convertir
un loss node en un nombre Python). Notez que le modèle peut prendre plusieurs minutes à être entrainé.
Pour tester votre implémentation, utilisez l'auto-correcteur comme suit :
python autograder.py -q q1
Critère | Points |
Code fonctionnel | 3 |
Perte sur le jeu de test <= 0.02 | 5 |
Qualité / lisibilité du code | 1 |
Pas de boucles for inutiles (code vectorisé) | 1 |
Total | 10 |
Pour cette question, vous allez entrainer un réseau pour classifier des articles de mode du jeu de données Fashion-MNIST qui contient 60,000 exemples d'entraînement et 10,000 exemples de test.
Chaque image est de taille $28 \times 28$ pixels, representée par un vecteur réel de dimension $784$. Chaque cible fournie est un vecteur de dimension $10$ avec des zéros pour toutes les valeurs à l'exception d'un $1$ à la position de la classe correcte (One-Hot Encoding).
Complétez l'implémentation de la classe FashionClassificationModel
dans models.py
. FashionClassificationModel.run()
devrait retourner un node $\text{batch_size} \times 10$ contenant des scores, où un score plus élevé indique une plus grande probabilité qu'un article appartienne à une classe particulière. Vous devez utiliser nn.SoftmaxLoss
pour la perte. N'utilisez pas de fonction d'activation (ReLU) sur la dernière couche de votre réseau.
Pour cette question et la question 3, en plus des données d'entrainement, il y a aussi des jeux de données de validation et de test. Vous pouvez utiliser dataset.get_validation_accuracy()
pour calculer la précision de votre modèle sur le jeu de données de validation, ce qui peut être utile pour décider quand arrêter l'entrainement. Le jeu de données de test sera utilisé par l'auto-correcteur.
Pour recevoir les points sur cette question, votre modèle doit atteindre une précision de 80% (ou plus) sur le jeu de test. Notez que l'auto-correcteur vous note sur la précision sur le jeu de test. Ainsi, même si votre modèle atteint 80% de précision sur le jeu de validation, il peut obtenir moins sur le jeu de test. Il peut donc être utile d'arrêter l'entrainement au dela de 80% (81, 82%).
Tâches :
FashionClassificationModel.__init__
avec toute initialization nécessaire;FashionClassificationModel.run
pour renvoyer un noeud $\text{batch_size} \times 10$ qui
représente les predictions de votre modèle;FashionClassificationModel.get_loss
qui retourne la perte pour les entrées et les cibles données;FashionClassificationModel.train
, qui doit entrainer votre modèle en utilisant des
mise-à-jours basées sur les gradients.Pour tester votre implémentation, utilisez l'auto-correcteur comme suit :
python autograder.py -q q2
À titre de référence voici à quoi correspondent les labels :
Label | Article |
0 | T-shirt/top |
1 | Trouser |
2 | Pullover |
3 | Dress |
4 | Coat |
5 | Sandal |
6 | Shirt |
7 | Sneaker |
8 | Bag |
9 | Ankle boot |
Critère | Points |
Code fonctionnel | 3 |
Précision sur le jeu de test >= 80% | 5 |
Qualité / lisibilité du code | 1 |
Pas de boucles for inutiles (code vectorisé) | 1 |
Total | 10 |
L'identification de langues consiste à identifier, étant donné un texte, quelle est la langue utilisée. Par exemple, votre navigateur web peut être capable de détecter que vous consultez une page dans une langue étrangère et vous proposer de la traduire automatiquement pour vous. Google Chrome, par exemple, utilise un réseau de neurones pour implémenter cette fonctionnalité.
Dans ce projet, vous allez construire un réseau de neurones qui identifie la langue un mot à la fois. Le jeu de données est composé de mots en 5 langues, par exemple :
Mot | Langue |
discussed | Anglais |
eternidad | Espagnol |
itseänne | Finlandais |
paleis | Néerlandais |
mieszkać | Polonais |
Chaque mot pouvant avoir un nombre de lettres différent, votre modèle nécessite une architecture qui peut manipuler des entrées à longueur variable.
Au lieu d'une seule entrée $x$ (comme dans les questions précédentes), vous aurez une entrée pour chaque lettre : $x_0, x_1, ..., x_{L - 1}$ ou $L$ est la longueur du mot.
Vous commencerez par appliquer un réseau $f_{\text{initial}}$ qui ressemble aux réseaux feed-forward des questions précédentes. Ce réseau accepte une entrée $x_0$ et calcule
un vecteur de sortie $h_1$ de dimension $d$ :
\[ h_1 = f_{\text{initial}}(x_0) \]
Ensuite, vous allez combiner la sortie de cette première étape avec la prochaine lettre du mot, générant un vecteur resumant les deux premières lettres. Pour cela,
vous allez appliquer un sous-réseau qui va accepter en entrée une lettre et retourne un état caché, mais qui depend aussi du précédent état caché $h_1$. On note ce sous-réseau $f$.
\[ h_2 = f(h_1, x_1) \]
Ce schema continue pour toutes les lettres dans le mot. L'état caché à chaque étape résume toutes les lettres que le réseau a vu jusqu'à présent :
\[
h_1 = f_{\text{initial}}(x_0)\\
h_2 = f(h_1, x_1)\\
h_3 = f(h_2, x_2)\\
\vdots
\]
Tout au long de ces calculs, la fonction $f(\cdot,\cdot)$ est le meme morceau du réseau de neurones et utilise les mêmes paramètres d'entrainement; $f_{\text{initial}}$ va aussi partager certains des paramètres de $f(\cdot,\cdot)$.
De cette manière, les paramètres utilisés pour traiter les mots de taille différente seront partagés. Vous pouvez implémenter cela en utilisant une boucle for
sur les entrées xs
,
où chaque itération de la boucle calcule soit $f_{\text{initial}}$ soit $f$.
La technique que nous venons de voir s'appelle un réseau de neurones récurrent (RNN). Voici un diagramme d'un RNN :
Ici, un RNN est utilisé pour encoder le mot "cat" en un vecteur de taille fixe $h_3$.
Après que le RNN a traité la longueur totale de l'entrée, le RNN a encodé le mot en entrée (qui est de taille variable) en un vecteur de taille fixe $h_L$, où L est la longueur du mot. Ce "résumé vectoriel" du mot en entrée peut maintenant être passé en entrée de couches additionnelles pour énérer des scores de classification, afin de classifier le mot selon sa langue.
Bien que les équations précédentes sont exprimées pour un mot, en pratique vous devez utiliser des batchs, pour des questions de performance. Par simplicité, le code du projet garanti que tous les mots dans un même batch ont la même taille. Sous forme de batchs donc, un état caché $h_i$ est remplacé par une matrice $H_i$ de dimension $\text{batch_size} \times D$.
La principale difficulté de cette question est le design de la fonction récurrente $f(h,x)$. Voici quelques conseils :
nn.Add
. Autrement dit, vous devriez remplacer un calcul de la forme
z = nn.Linear(x, W)
par un calcul de la forme z = nn.Add(nn.Linear(x, W), nn.Linear(h, W_hidden))
;
Avertissement : Le jeu de données utilisé a été généré automatiquement. Il peut contenir des erreurs, un language inapproprié ou offensant. Si vous trouvez des instances de mots de ce type, veuillez les rapporter aux correcteurs/instructeurs.
Tâches :
LanguageIDModel.__init__
avec toute initialization nécessaire;LanguageIDModel.run
pour renvoyer un noeud $\text{batch_size} \times 5$ qui
représente les predictions de votre modèle;LanguageIDModel.get_loss
qui retourne la perte pour les entrées et les cibles données;LanguageIDModel.train
, qui doit entrainer votre modèle en utilisant des
mise-à-jours basées sur les gradients.Pour recevoir tous les points, votre architecture doit être capable d'atteindre une précision d'au moins 81% sur le jeu de test.
Pour tester votre implémentation, utilisez l'auto-correcteur comme suit :
python autograder.py -q q3
Critère | Points |
Code fonctionnel | 3 |
Précision sur le jeu de test >= 81% | 5 |
Qualité / lisibilité du code | 2 |
Total | 10 |
Pour cette dernière question, vous allez concevoir l'architecture d'un CNN. Pour ce faire, contrairement aux questions précédentes, vous allez utiliser la bibliothèque PyTorch.
PyTorch vous permet de définir un modèle comme une suite de couches à l'aide de la classe Sequential
(documentation). Cette classe prend
dans son constructeur les différentes couches du modèle, puis les connecte automatiquement. Ce tutoriel pourrait s'avérer utile.
Parmi les couches dont vous pourriez avoir besoin (vous pouvez aussi en utiliser d'autres si vous le souhaitez) :
Conv2d
(documentation) : réalise une convolution sur les entrées 2D;MaxPool2d
(documentation) : réalise un max-pooling sur les entrées 2D;AvgPool2d
(documentation) : réalise un average-pooling sur les entrées 2D;ReLU
(documentation) : applique la fonction ReLU sur toutes les entrées;Tanh
(documentation) : applique la fonction Tanh sur toutes les entrées;Dropout
(documentation) : désactive aléatoirement certains poids pour éviter le sur-apprentissage;Flatten
(documentation) : cette couche particulière change les dimensions de son entrée pour transformer une matrice (ou un tenseur) en un vecteur;Linear
(documentation) : réalise une convolution 2D sur les entrées;
Vous allez devoir commencer votre modèle par une ou plusieurs couches à convolution, puis finir votre modèle par une ou plusieurs couches linéaires. Entre vos couches à convolution et vos couches linéaires, vous devrez mettre la couche Flatten
afin de redimensionner les entrées pour
les couches linéaires.
Vous ne devez compléter QUE la création du modèle. La boucle d'entrainement a déjà été concue pour vous.
Tâches :
self.model = torch_nn.Sequential(...)
dans la classe PyTorchCNNFashionClassificationModel
.Pour recevoir tous les points, votre architecture doit être capable d'atteindre une précision d'au moins 84% sur le jeu de test.
Pour tester votre implémentation, utilisez l'auto-correcteur comme suit :
python autograder.py -q q4
Critère | Points |
Code fonctionnel | 3 |
Précision sur le jeu de test >= 84% | 5 |
Qualité / lisibilité du code | 2 |
Total | 10 |
Membre 1 | Membre 2 |
---|---|
Proulx, Hugo | Turcotte, Raphaël |
Pion, Raphaël | Charbonneau, Victor |
Pépin, Pierre-Luc | Bourgeois, Thomas |
Bellavance, Nicolas | Desfossés, Alexandre |
Grenier, Philippe-Olivier | Dia, Adam |
Tétreault, Etienne | Vallières, Xavier |
Rouabah, Lokman | Boulanger, Bastien |
Yahya, Mohamed | Breton Corona, Eduardo Yvan |
Duchesneau, Paul | Tientcheu Tchako, David Jeeson |
Lessard, Nathan | Crozet, Thomas |
Allard, Cloé | Gendreau, Tommy |
Lamothe-Morin, Zoé | Bergeron, Marc-Olivier |
Boutin, Karl | Giasson, Frédéric |
Girard Hivon, Maxime | Krid, Ahmed Bahaedine |
Moulay Abdallah, Mustapha | - |
Gauthier, Carl | Lavallée, Louis |
Philion, Guillaume | Mailhot, Christophe |
Carignan, Benjamin | Ménard Tétreault, Yuhan |