iid-course-utils 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: iid-course-utils
3
+ Version: 0.1
4
+ Summary: A library for internal course tools used in IID ULaval courses.
5
+ Project-URL: Homepage, https://github.com/iid-ulaval/iid-course-utils
6
+ Author-email: Marouane Yassine <marouane.yassine.1@ulaval.ca>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.8
10
+ Requires-Dist: matplotlib
11
+ Requires-Dist: numpy
12
+ Requires-Dist: pandas
13
+ Requires-Dist: poutyne
14
+ Requires-Dist: requests
15
+ Requires-Dist: scikit-learn
16
+ Requires-Dist: seaborn
17
+ Requires-Dist: torch
18
+ Requires-Dist: torchtext
19
+ Description-Content-Type: text/markdown
20
+
21
+ # A library for internal course tools used in IID ULaval courses
@@ -0,0 +1,17 @@
1
+ iidcourse/__init__.py,sha256=ynyfXCEoiCL7SmyKKP3iGJ1MazPCkwmg7pC7r0ZnFhw,71
2
+ iidcourse/convolution/__init__.py,sha256=WG5hjS6Yh7b4S4DoDlUjyVgwd64N9c4dfYqvdakaB6Y,169
3
+ iidcourse/convolution/architectures.py,sha256=1QUG3JQ3Guf3_pN7HgAftQojUK8lB2fcoYubMCZN-Zo,8905
4
+ iidcourse/convolution/callbacks.py,sha256=AANIzBiU1bsUXaGr8hhvuJ6ZliqV0a46Niw0JG9PEus,358
5
+ iidcourse/convolution/datasets.py,sha256=ZIdk6L1cCA4rXKR_gRFV4P9ob46B9CjXoS1hl6yvIWc,2752
6
+ iidcourse/convolution/visualisation.py,sha256=rHK05tifOsokoHhik_k2enkP3n70HcT1fE8A1kZv2Qg,8807
7
+ iidcourse/convolution/weights_init.py,sha256=lcZJ9_kb-CF4oN62bDb0m_jW5qU5KyET8_2sC1MAWxs,295
8
+ iidcourse/nlp/__init__.py,sha256=gPzMJEQubNzKJn0MZyLY8HEmVwVaPHXFDidNHLr6tKE,114
9
+ iidcourse/nlp/data_transformer.py,sha256=YkUm2Kw-NlALdmF1ZF4ZRvCLj13jG8qOTkc7HhOh__M,2186
10
+ iidcourse/nlp/datasets.py,sha256=j9ZiY8DcrScALLnAnUeADda_4XCGcNv85ANRQHP7oMk,1244
11
+ iidcourse/nlp/evaluate.py,sha256=IKzUnI7iuzSlifiIoyWD26aSb3LvHP1of5xP8d0ApGM,1168
12
+ iidcourse/resources/__init__.py,sha256=ybwE_sLeW8VT7I7HLeegelFOVtTFS0tLuN4TiumsI_c,27
13
+ iidcourse/resources/files_setup.py,sha256=EiX34P9eYwIEG4UD0cs_z7rMqYvEZevJNvdiXBiT6_Q,3104
14
+ iid_course_utils-0.1.dist-info/METADATA,sha256=jVKxqSTyMecR6OwBqzBCyYUxdq4xD1jjZcj-0kAVG30,651
15
+ iid_course_utils-0.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
16
+ iid_course_utils-0.1.dist-info/licenses/LICENSE,sha256=m0Ej54eG_LZbixr0A-xPunQSyCzhnbxw29eeTZaRNEY,1060
17
+ iid_course_utils-0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 PAX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
iidcourse/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .nlp import *
2
+ from .convolution import *
3
+ from .resources import *
@@ -0,0 +1,6 @@
1
+ # pylint: disable=wildcard-import
2
+ from .architectures import *
3
+ from .callbacks import *
4
+ from .datasets import *
5
+ from .visualisation import *
6
+ from .weights_init import *
@@ -0,0 +1,234 @@
1
+ from functools import partial
2
+
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+
8
+
9
+ class ClassicNet(nn.Module):
10
+ def __init__(self, num_layers):
11
+ super().__init__()
12
+ layers = [nn.Conv2d(3, 100, 3, padding=1), nn.ReLU()]
13
+ self.num_layers = num_layers
14
+ for layer_number in range(2, num_layers + 1):
15
+ layers.append(nn.Conv2d(100, 100, 3, padding=1))
16
+ layers.append(nn.ReLU())
17
+ if layer_number in [3, 5]:
18
+ layers.append(nn.MaxPool2d(2))
19
+
20
+ layers.append(nn.Flatten())
21
+ layers.append(nn.Linear(100 * 8 * 8, 10))
22
+
23
+ self.model = nn.Sequential(*layers)
24
+
25
+ def forward(self, x):
26
+
27
+ for layer in self.model:
28
+ x = layer(x)
29
+
30
+ return x
31
+
32
+ def eval_weight_distribution(self, x):
33
+ k = 2000 # Chiffre de valeurs aléatoires
34
+ entree = {}
35
+ indice = 0
36
+ for layer in self.model:
37
+ if isinstance(layer, torch.nn.modules.conv.Conv2d):
38
+ indice += 1
39
+ if indice in (1, self.num_layers - 1): # Première ou dernière couche
40
+ echantillon = nn.Flatten(0)(x.detach().cpu())
41
+ entree[f"Conv_{indice}"] = np.random.choice(echantillon, size=k)
42
+
43
+ x = layer(x)
44
+
45
+ return x, entree
46
+
47
+
48
+ class ResidualLayer(nn.Module):
49
+ def __init__(self):
50
+ super().__init__()
51
+ self.conv1 = nn.Conv2d(100, 100, 3, padding=1)
52
+
53
+ self.conv2 = nn.Conv2d(100, 100, 3, padding=1)
54
+
55
+ def forward(self, x):
56
+ res = x
57
+ x = F.relu((self.conv1(x)))
58
+ x = self.conv2(x)
59
+ x = F.relu(x + res)
60
+ return x
61
+
62
+
63
+ class ResNet(nn.Module):
64
+ def __init__(self, nb_bloc_residuelle):
65
+ super().__init__()
66
+ assert nb_bloc_residuelle > 2
67
+
68
+ self.conv1 = nn.Conv2d(3, 100, 3, padding=1)
69
+ self.bn1 = nn.BatchNorm2d(100)
70
+
71
+ self.layers = nn.ModuleList([ResidualLayer() for _ in range(nb_bloc_residuelle)])
72
+ self.fc1 = nn.Linear(100 * 8 * 8, 10)
73
+
74
+ def forward(self, x):
75
+ x = F.relu(self.bn1(self.conv1(x)))
76
+
77
+ for i, layer in enumerate(self.layers):
78
+ x = layer(x)
79
+ if i <= 1:
80
+ x = F.max_pool2d(x, 2)
81
+
82
+ x = x.flatten(1)
83
+ x = self.fc1(x)
84
+ return x
85
+
86
+
87
+ """
88
+ The code below was copied from the "Image-to-Image Translation in PyTorch" repository
89
+
90
+ COPYRIGHT
91
+
92
+ All contributions from the https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/cf4191a3a4cc77fdffa5c0a8246c346049958e78/models/networks.py#L468.
93
+ authors.
94
+ Copyright (c) 2017, Jun-Yan Zhu and Taesung Park
95
+ All rights reserved.
96
+
97
+ Redistribution and use in source and binary forms, with or without
98
+ modification, are permitted provided that the following conditions are met:
99
+
100
+ * Redistributions of source code must retain the above copyright notice, this
101
+ list of conditions and the following disclaimer.
102
+
103
+ * Redistributions in binary form must reproduce the above copyright notice,
104
+ this list of conditions and the following disclaimer in the documentation
105
+ and/or other materials provided with the distribution.
106
+
107
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
108
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
109
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
110
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
111
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
112
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
113
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
114
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
115
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
116
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
117
+
118
+
119
+ --------------------------- LICENSE FOR pix2pix --------------------------------
120
+ BSD License
121
+
122
+ For pix2pix software
123
+ Copyright (c) 2016, Phillip Isola and Jun-Yan Zhu
124
+ All rights reserved.
125
+
126
+ Redistribution and use in source and binary forms, with or without
127
+ modification, are permitted provided that the following conditions are met:
128
+
129
+ * Redistributions of source code must retain the above copyright notice, this
130
+ list of conditions and the following disclaimer.
131
+
132
+ * Redistributions in binary form must reproduce the above copyright notice,
133
+ this list of conditions and the following disclaimer in the documentation
134
+ and/or other materials provided with the distribution.
135
+
136
+ ----------------------------- LICENSE FOR DCGAN --------------------------------
137
+ BSD License
138
+
139
+ For dcgan.torch software
140
+
141
+ Copyright (c) 2015, Facebook, Inc. All rights reserved.
142
+
143
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
144
+ following conditions are met:
145
+
146
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following
147
+ disclaimer.
148
+
149
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
150
+ disclaimer in the documentation and/or other materials provided with the distribution.
151
+
152
+ Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from
153
+ this software without specific prior written permission.
154
+
155
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
156
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
157
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
158
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
159
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
160
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
161
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
162
+ DAMAGE.
163
+ """
164
+
165
+
166
+ class UNetSkipConnectionBlock(nn.Module):
167
+ """Defines the U-Net submodule with skip connection.
168
+ X -------------------identity----------------------
169
+ |-- downsampling -- |submodule| -- upsampling --|
170
+ """
171
+
172
+ def __init__(
173
+ self,
174
+ outer_nc,
175
+ inner_nc,
176
+ input_nc=None,
177
+ submodule=None,
178
+ outermost=False,
179
+ innermost=False,
180
+ norm_layer=nn.BatchNorm2d,
181
+ use_dropout=False,
182
+ ):
183
+ """Construct a Unet submodule with skip connections.
184
+ Parameters:
185
+ outer_nc (int): The number of filters in the outer conv layer.
186
+ inner_nc (int): The number of filters in the inner conv layer.
187
+ input_nc (int): The number of channels in input images/features.
188
+ submodule (UnetSkipConnectionBlock): Previously defined submodules.
189
+ outermost (bool): If this module is the outermost module.
190
+ innermost (bool): If this module is the innermost module.
191
+ norm_layer: Normalization layer.
192
+ use_dropout (bool): If use dropout layers.
193
+ """
194
+ super().__init__()
195
+ self.outermost = outermost
196
+ if isinstance(norm_layer, partial):
197
+ use_bias = norm_layer.func == nn.InstanceNorm2d
198
+ else:
199
+ use_bias = norm_layer == nn.InstanceNorm2d
200
+ if input_nc is None:
201
+ input_nc = outer_nc
202
+ downconv = nn.Conv2d(input_nc, inner_nc, kernel_size=4, stride=2, padding=1, bias=use_bias)
203
+ downrelu = nn.LeakyReLU(0.2, True)
204
+ downnorm = norm_layer(inner_nc)
205
+ uprelu = nn.ReLU(True)
206
+ upnorm = norm_layer(outer_nc)
207
+
208
+ if outermost:
209
+ upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc, kernel_size=4, stride=2, padding=1)
210
+ down = [downconv]
211
+ up = [uprelu, upconv, nn.Tanh()]
212
+ model = down + [submodule] + up
213
+ elif innermost:
214
+ upconv = nn.ConvTranspose2d(inner_nc, outer_nc, kernel_size=4, stride=2, padding=1, bias=use_bias)
215
+ down = [downrelu, downconv]
216
+ up = [uprelu, upconv, upnorm]
217
+ model = down + up
218
+ else:
219
+ upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc, kernel_size=4, stride=2, padding=1, bias=use_bias)
220
+ down = [downrelu, downconv, downnorm]
221
+ up = [uprelu, upconv, upnorm]
222
+
223
+ if use_dropout:
224
+ model = down + [submodule] + up + [nn.Dropout(0.5)]
225
+ else:
226
+ model = down + [submodule] + up
227
+
228
+ self.model = nn.Sequential(*model)
229
+
230
+ def forward(self, x):
231
+ if self.outermost:
232
+ return self.model(x)
233
+ else: # Add skip connections
234
+ return torch.cat([x, self.model(x)], 1)
@@ -0,0 +1,11 @@
1
+ from poutyne import Callback
2
+
3
+
4
+ class SauvegardeLR(Callback):
5
+ def __init__(self, optimizer):
6
+ super().__init__()
7
+ self.optimizer = optimizer
8
+ self.lr_par_epoch = []
9
+
10
+ def on_epoch_begin(self, epoch_number, logs): ## À chaque début d'époque
11
+ self.lr_par_epoch.append(self.optimizer.state_dict()["param_groups"][0]["lr"])
@@ -0,0 +1,91 @@
1
+ import pickle as pk
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import torch
6
+ from torch.utils.data import Dataset
7
+
8
+
9
+ class FaceDataset(Dataset):
10
+ def __init__(self, csv_file, transform=None, columns=None):
11
+
12
+ self.data = pd.read_csv(csv_file)
13
+
14
+ if columns is None:
15
+ self.columns = ["age", "ethnicity", "gender"]
16
+ else:
17
+ self.columns = columns
18
+
19
+ self.data.drop(columns={"img_name"}, inplace=True)
20
+ self.data["pixels"] = self.data["pixels"].apply(
21
+ lambda x: np.array(x.split(), dtype="float32").reshape((1, 48, 48)) / 255
22
+ )
23
+ self.data["age"] = self.data["age"].apply(lambda x: np.array([x], dtype="float32"))
24
+ self.X = torch.Tensor(self.data["pixels"])
25
+
26
+ self.transform = transform
27
+
28
+ def __len__(self):
29
+ return len(self.data)
30
+
31
+ def __getitem__(self, indice):
32
+ if torch.is_tensor(indice):
33
+ indice = indice.tolist()
34
+ image = self.X[indice]
35
+ if len(self.columns) > 1:
36
+ attribute = torch.Tensor([float(self.data.iloc[indice][i]) for i in self.columns])
37
+ else:
38
+ attribute = self.data.iloc[indice][self.columns[0]]
39
+ sample = (image, attribute)
40
+
41
+ if self.transform:
42
+ sample = (self.transform(sample[0]), attribute)
43
+
44
+ return sample
45
+
46
+
47
+ class EchantillonCIFAR10(Dataset):
48
+ """
49
+ Échantillon du jeu de donnée CIFAR10. L'échantillon comprend 10 000 données d'entraînements
50
+ et 2 000 de tests.
51
+ Args:
52
+ train (bool): Si True, prend les données d'entraînements, sinon les données de tests.
53
+ transform (callable, optional): Une function/transform qui prend le target et le transforme.
54
+ course (str, optional): Le sigle de la formation.
55
+
56
+ """
57
+
58
+ def __init__(self, train=True, transform=None, course='gif-u019'):
59
+ train_data_path = './CIFAR10_train_10000_sample.pk'
60
+ test_data_path = './CIFAR10_test_2000_sample.pk'
61
+ if train:
62
+ self.echantillon = pk.load(open(train_data_path, "rb"))
63
+ else:
64
+ self.echantillon = pk.load(open(test_data_path, "rb"))
65
+
66
+ self.transform = transform
67
+ self.classes = [
68
+ "avion",
69
+ "automobile",
70
+ "oiseau",
71
+ "chat",
72
+ "chevreuil",
73
+ "chien",
74
+ "grenouille",
75
+ "cheval",
76
+ "bateau",
77
+ "camion",
78
+ ]
79
+
80
+ def __len__(self):
81
+ return len(self.echantillon)
82
+
83
+ def __getitem__(self, indice):
84
+
85
+ if torch.is_tensor(indice):
86
+ indice = indice.tolist()
87
+ img, target = self.echantillon[indice]
88
+ if self.transform is not None:
89
+ img = self.transform(img)
90
+
91
+ return img, target
@@ -0,0 +1,246 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+ import seaborn as sns
4
+ import torch
5
+
6
+ from poutyne import Callback
7
+
8
+
9
+ def visualiser_difference_de_poids(poids_initiaux, poids_finaux):
10
+ """
11
+ Affiche un graphique de la différence absolue moyenne entre les poids initiaux et finaux par couche.
12
+ Args:
13
+ poids_initiaux (Dict[str, torch.Tensor] ): Un dictionnaire où les clés sont le nom de
14
+ la couche et leur valeur sont les poids.
15
+ poids_finaux (Dict[str, torch.Tensor] ): Un dictionnaire où les clés sont le nom de
16
+ la couche et leur valeur sont les poids.
17
+ """
18
+
19
+ difference_etat = {}
20
+ nb_couches = len(poids_initiaux.keys())
21
+
22
+ for etat_initial_couche, etat_final_couche in zip(poids_initiaux.items(), poids_finaux.items()):
23
+ difference_etat[etat_initial_couche[0]] = torch.abs(
24
+ torch.flatten(
25
+ (etat_final_couche[1].cpu() - etat_initial_couche[1].cpu()).detach().clone(),
26
+ 0,
27
+ )
28
+ ).tolist()
29
+
30
+ moyenne = [np.mean(poids_couche) for poids_couche in difference_etat.values()]
31
+ couche = [np.mean(i) for i in range(len(difference_etat.values()))]
32
+
33
+ max_moyenne = np.max(moyenne)
34
+
35
+ sns.lineplot(x=couche, y=moyenne)
36
+ plt.title(
37
+ f"Valeur moyenne de la différence absolue des poids initiaux et finaux par couche "
38
+ f"dans une architecture de {nb_couches} couches"
39
+ )
40
+ plt.xlabel("Indice de la couche")
41
+ plt.ylabel("différence")
42
+ plt.ylim(0, max_moyenne)
43
+ plt.show()
44
+
45
+
46
+ class SauvegarderPoids(Callback):
47
+ """
48
+ This callback multiply the loss temperature with a decay before
49
+ each batch.
50
+
51
+ Args:
52
+ celoss_with_temp (CrossEntropyLossWithTemperature): the loss module.
53
+ decay (float): The value of the temperature decay.
54
+ """
55
+
56
+ def __init__(self):
57
+ super().__init__()
58
+ self.liste_poids = []
59
+
60
+ def on_epoch_begin(self, epoch_number, logs): ## À chaque début d'époque
61
+ poids = tuple(self.model.network.parameters())[0][0].clone().detach().cpu()
62
+ self.liste_poids.append(poids)
63
+
64
+
65
+ def show_2d_function(
66
+ fct,
67
+ min_val=-5,
68
+ max_val=5,
69
+ mesh_step=0.01,
70
+ *,
71
+ optimal=None,
72
+ bar=True,
73
+ ax=None,
74
+ **kwargs,
75
+ ):
76
+ # pylint: disable=blacklisted-name
77
+ """
78
+ Trace les courbes de niveau d'une fonction 2D.
79
+ Args:
80
+ fct (Callable[torch.Tensor, torch.Tensor]): Fonction objectif qui prend en paramètre un tenseur Nx2
81
+ correspondant à N paramètres pour lesquels on veut obtenir la valeur de la fonction.
82
+ optimal (torch.Tensor): La valeur optimale des poids pour la fonction objectif.
83
+ """
84
+ w1_values = torch.arange(min_val, max_val + mesh_step, mesh_step)
85
+ w2_values = torch.arange(min_val, max_val + mesh_step, mesh_step)
86
+
87
+ w2, w1 = torch.meshgrid(w2_values, w1_values, indexing='ij')
88
+ w_grid = torch.stack((w1.flatten(), w2.flatten()))
89
+ fct_values = fct(w_grid).view(w1_values.shape[0], w2.shape[0]).numpy()
90
+
91
+ w1_values, w2_values = w1_values.numpy(), w2_values.numpy()
92
+
93
+ if ax is not None:
94
+ plt.sca(ax)
95
+ if "cmap" not in kwargs:
96
+ kwargs["cmap"] = "RdBu"
97
+ plt.contour(w1_values, w2_values, fct_values, 40, **kwargs)
98
+ plt.xlim((min_val, max_val))
99
+ plt.ylim((min_val, max_val))
100
+ plt.xlabel("$w_1$")
101
+ plt.ylabel("$w_1$")
102
+
103
+ if bar:
104
+ plt.colorbar()
105
+
106
+ if optimal is not None:
107
+ plt.scatter(*optimal.numpy(), s=200, marker="*", c="r")
108
+
109
+
110
+ def show_2d_trajectory(w_history, fct, min_val=-5, max_val=5, mesh_step=0.5, *, optimal=None, ax=None):
111
+ """
112
+ Trace le graphique de la trajectoire de descente en gradient en 2D.
113
+ Args:
114
+ w_history: L'historique de la valeur des poids lors de l'entraînement.
115
+ fct (Callable[torch.Tensor, torch.Tensor]): Fonction objectif qui prend en paramètre un tenseur Nx2
116
+ correspondant à N paramètres pour lesquels on veut obtenir la valeur de la fonction.
117
+ optimal (torch.Tensor): La valeur optimale des poids pour la fonction objectif.
118
+ """
119
+ show_2d_function(fct, min_val, max_val, mesh_step, optimal=optimal, ax=ax)
120
+
121
+ if len(w_history) > 0:
122
+ trajectory = np.array(w_history)
123
+ plt.plot(trajectory[:, 0], trajectory[:, 1], "o--", c="g")
124
+
125
+ plt.title("Trajectoire de la descente en gradient")
126
+ plt.xlabel("$w_1$")
127
+ plt.ylabel("$w_2$")
128
+
129
+
130
+ def show_learning_curve(loss_list, loss_opt=None, ax=None):
131
+ """
132
+ Trace le graphique des valeurs de la fonction objectif lors de l'apprentissage.
133
+ Args:
134
+ loss_list: L'historique de la valeur de la perte lors de l'entraînement.
135
+ loss_opt: La valeur optimale de perte.
136
+ """
137
+ if ax is not None:
138
+ plt.sca(ax)
139
+ plt.plot(
140
+ np.arange(1, len(loss_list) + 1),
141
+ loss_list,
142
+ "o--",
143
+ c="g",
144
+ label="$F(\\mathbf{w})$",
145
+ )
146
+ if loss_opt is not None:
147
+ plt.plot([1, len(loss_list)], 2 * [loss_opt], "*--", c="r", label="optimal")
148
+ plt.title("Valeurs de la fonction objectif")
149
+ plt.xlabel("Itérations")
150
+ plt.legend()
151
+
152
+
153
+ def show_optimization(w_history, loss_history, fct, optimal=None, title=None):
154
+ """
155
+ Trace deux graphiques montrant le trajet de l'optimisation d'une fonction objectif 2D. Le premier montre la valeur
156
+ des poids lors de l'optimisation. Le deuxième montre la valeur de la perte lors de l'optimisation.
157
+ Args:
158
+ w_history: L'historique des poids lors de l'optimisation
159
+ loss_history: L'historique de la valeur de la fonction perte.
160
+ fct (Callable[torch.Tensor, torch.Tensor]): Fonction objectif qui prend en paramètre un tenseur Nx2
161
+ correspondant à N paramètres pour lesquels on veut obtenir la valeur de la fonction.
162
+ optimal (torch.Tensor): La valeur optimale des poids pour la fonction objectif.
163
+ """
164
+ fig, axes = plt.subplots(1, 2, figsize=(14.5, 4))
165
+ if title is not None:
166
+ fig.suptitle(title)
167
+ show_2d_trajectory(w_history, fct, optimal=optimal, ax=axes[0])
168
+ show_learning_curve(loss_history, loss_opt=fct(optimal).numpy(), ax=axes[1])
169
+
170
+
171
+ def plot_image_samples(dataset):
172
+ """
173
+ Affiche certaines images d'un jeu de données
174
+ """
175
+ classes = dataset.classes
176
+ num_samples = 15
177
+ indices = np.random.choice(len(dataset), size=num_samples, replace=False)
178
+ NROWS = 3
179
+ NCOLS = 5
180
+
181
+ assert len(indices) <= NROWS * NCOLS, f"Impossible d'afficher plus de {NROWS * NCOLS} images."
182
+
183
+ samples = [dataset[i] for i in indices]
184
+ images, targets = zip(*samples)
185
+ targets = list(targets)
186
+
187
+ # Affichage des images avec leurs étiquettes
188
+ fig, axes = plt.subplots(NROWS, NCOLS, sharex=True, sharey=True)
189
+ for ax, i, image, target in zip(axes.flat, indices, images, targets):
190
+ ax.imshow(image, cmap="gray")
191
+
192
+ target_class = classes[target]
193
+ ax.set_xlabel(f"Exemple {i}:\n" + target_class, fontsize=8)
194
+ ax.set_xticks([])
195
+ ax.set_yticks([])
196
+
197
+ fig.tight_layout()
198
+ plt.show()
199
+
200
+
201
+ def visualiser_convolution(reseau, donnee, nb_image_max_par_couche):
202
+ x = torch.unsqueeze(list(donnee)[0][0], 0)
203
+ image_initiale = x[0].numpy().transpose((1, 2, 0))
204
+ x = x.cuda()
205
+ print("Image initiale :")
206
+ plt.imshow(image_initiale)
207
+ plt.show()
208
+ with torch.no_grad():
209
+ conv = 0
210
+ pool = 0
211
+
212
+ for layer in reseau.children():
213
+ x = layer(x)
214
+
215
+ if isinstance(layer, torch.nn.Conv2d):
216
+ conv += 1
217
+ print(f"Convolution {conv}")
218
+ _, axes = plt.subplots(
219
+ min(x.shape[1] // 4, nb_image_max_par_couche // 4),
220
+ 4,
221
+ constrained_layout=True,
222
+ )
223
+ for feature in range(min(x.shape[1], nb_image_max_par_couche)):
224
+ image_conv = x[0, feature, :, :].clone().cpu()
225
+
226
+ try:
227
+ axes[feature // 4, feature % 4].imshow(image_conv)
228
+ except IndexError:
229
+ axes[feature % 4].imshow(image_conv)
230
+ plt.show()
231
+ if isinstance(layer, (torch.nn.modules.pooling.MaxPool2d, torch.nn.modules.pooling.AvgPool2d)):
232
+ pool += 1
233
+ print(f"Pooling {pool}")
234
+ _, axes = plt.subplots(
235
+ min(x.shape[1] // 4, nb_image_max_par_couche // 4),
236
+ 4,
237
+ constrained_layout=True,
238
+ )
239
+ for feature in range(min(x.shape[1], nb_image_max_par_couche)):
240
+ imageConv = x[0, feature, :, :]
241
+ try:
242
+ axes[feature // 4, feature % 4].imshow(image_conv)
243
+ except IndexError:
244
+ axes[feature % 4].imshow(image_conv)
245
+
246
+ plt.show()
@@ -0,0 +1,10 @@
1
+ import torch
2
+ from torch import nn
3
+
4
+
5
+ def weights_init(m):
6
+ if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
7
+ torch.nn.init.normal_(m.weight, 0.0, 0.02)
8
+ if isinstance(m, nn.BatchNorm2d):
9
+ torch.nn.init.normal_(m.weight, 0.0, 0.02)
10
+ torch.nn.init.constant_(m.bias, 0)
@@ -0,0 +1,4 @@
1
+ # pylint: disable=wildcard-import
2
+ from .evaluate import *
3
+ from .data_transformer import *
4
+ from .datasets import *
@@ -0,0 +1,104 @@
1
+ from abc import abstractmethod, ABC
2
+
3
+
4
+ class Transformer(ABC):
5
+ """
6
+ Abstract class for transformer over data.
7
+ """
8
+
9
+ def __init__(self):
10
+ self.__name__ = self.__class__.__name__
11
+
12
+ @abstractmethod
13
+ def transform(self, x):
14
+ """
15
+ Method to transform a text data.
16
+ :param x: (Union[str, List]) The data to be transform.
17
+ :return: (Union[str, List]) The transformed data.
18
+ """
19
+ pass
20
+
21
+ def fit(self, x):
22
+ """
23
+ Empty method to be compliant with Scikit-Learn interface.
24
+ """
25
+ return self
26
+
27
+ def __repr__(self):
28
+ return self.__name__
29
+
30
+
31
+ class NewLineStrip(Transformer):
32
+ """
33
+ A filter to remove newline characters at the end of strings.
34
+ """
35
+
36
+ def transform(self, x):
37
+ return [i.strip('\n') for i in x]
38
+
39
+
40
+ class EmptyLineRemoval(Transformer):
41
+ """
42
+ A filter to remove empty lines in a list.
43
+ """
44
+
45
+ def transform(self, x):
46
+ return list(filter(None, x))
47
+
48
+
49
+ class WhiteSpaceStrip(Transformer):
50
+ """
51
+ A filter to remove whitespace characters at the end of strings.
52
+ """
53
+
54
+ def transform(self, x):
55
+ return [i.strip(' ') for i in x]
56
+
57
+
58
+ class PunctuationStrip(Transformer):
59
+ """
60
+ A filter to remove punctuation characters at the end of strings.
61
+ """
62
+
63
+ def transform(self, x):
64
+ return [i.strip("""."',!?-""") for i in x]
65
+
66
+
67
+ class StringRemove(Transformer):
68
+ """
69
+ A filter to remove punctuation characters in strings.
70
+ """
71
+
72
+ def __init__(self, characters):
73
+ super().__init__()
74
+ self.characters = characters
75
+
76
+ def transform(self, x):
77
+ return [i.replace(self.characters, "") for i in x]
78
+
79
+
80
+ class PunctuationRemoval(StringRemove):
81
+ """
82
+ A filter to remove punctuation characters in strings.
83
+ """
84
+
85
+ def __init__(self):
86
+ super().__init__("!")
87
+
88
+
89
+ class ThinSpaceRemoval(StringRemove):
90
+ """
91
+ A filter to remove punctuation characters in strings.
92
+ """
93
+
94
+ def __init__(self):
95
+ super().__init__("\u2009")
96
+
97
+
98
+ class LowerCaser(Transformer):
99
+ """
100
+ A simple wrapper for lower case strings.
101
+ """
102
+
103
+ def transform(self, x):
104
+ return [i.lower() for i in x]
@@ -0,0 +1,30 @@
1
+ from torch.utils.data import Dataset
2
+ from torchtext.data.utils import get_tokenizer
3
+
4
+
5
+ class TwentyNewsGroupDataset(Dataset):
6
+ def __init__(self, texts, labels, vectors):
7
+ tokenizer = get_tokenizer("spacy", language="en_core_web_sm")
8
+ self.texts = []
9
+ self.labels = []
10
+
11
+ for i in range(len(texts)):
12
+ # On tokenise le texte en éliminant d'abord les caractères d'espacement en trop au début et à la fin
13
+ tokens = tokenizer(texts[i].strip())
14
+ label = labels[i]
15
+ # Après le retrait des en-têtes, pieds de pages et citations, certains textes sont vides
16
+ # On ignore ces textes
17
+ if len(tokens) > 0:
18
+ self.texts.append(tokens)
19
+ self.labels.append(label)
20
+
21
+ # On conserve en mémoire la table de vecteurs
22
+ self.vectors = vectors
23
+
24
+ def __len__(self):
25
+ # On utilise la liste des textes pour déterminer le nombre d'exemples du jeu de données
26
+ return len(self.texts)
27
+
28
+ def __getitem__(self, idx):
29
+ # Pour obtenir la liste de vecteurs associé à un texte, on passe ses jetons à la table de vecteurs
30
+ return self.vectors.get_vecs_by_tokens(self.texts[idx]), self.labels[idx]
@@ -0,0 +1,43 @@
1
+ import time
2
+
3
+ from sklearn.metrics import accuracy_score
4
+
5
+
6
+ def evaluate(pipeline, train, test):
7
+ """
8
+ Cette fonction sera utilisée pour comparer la performance,
9
+ le temps d'entraînement et la taille du vocabulaire des différents pipelines
10
+ """
11
+ train_data, train_label = train
12
+ test_data, test_label = test
13
+
14
+ start_time = time.time()
15
+ pipeline.fit(train_data, train_label)
16
+ end_time = time.time()
17
+
18
+ predict_train = pipeline.predict(train_data)
19
+ predict_test = pipeline.predict(test_data)
20
+
21
+ accuracy_train = accuracy_score(predict_train, train_label)
22
+ accuracy_test = accuracy_score(predict_test, test_label)
23
+ train_time = end_time - start_time
24
+
25
+ return accuracy_train, accuracy_test, train_time
26
+
27
+
28
+ def evaluate_20newsgroups(pipeline, train, test):
29
+ """
30
+ Une fonction pour évaluer sur 20newsgroup
31
+ """
32
+ return evaluate(
33
+ pipeline,
34
+ (train.data, train.target),
35
+ (test.data, test.target),
36
+ )
37
+
38
+
39
+ def evaluate_weccr(pipeline, train, test):
40
+ """
41
+ Une fonction pour évaluer sur WECCR
42
+ """
43
+ return evaluate(pipeline, (train["text"], train["label"]), (test["text"], test["label"]))
@@ -0,0 +1 @@
1
+ from .files_setup import *
@@ -0,0 +1,99 @@
1
+ import os
2
+ import requests
3
+
4
+
5
+ def setup_module(partie_id: int, module_id: int, course_name: str):
6
+ _download_dependencies(partie_id, module_id, course_name)
7
+
8
+ _download_module_files(partie_id, module_id, course_name)
9
+
10
+
11
+ def _download_dependencies(partie_id: int, module_id: int, course_name: str):
12
+ requirements_path = (
13
+ f"formation/partie_{partie_id}/module_{module_id}/requirements.txt"
14
+ )
15
+
16
+ _download_github_file("iid-ulaval", course_name, requirements_path)
17
+
18
+
19
+ def _download_module_files(partie_id: int, module_id: int, course_name: str):
20
+ folder_path = f"formation/partie_{partie_id}/module_{module_id}/fichiers"
21
+
22
+ _download_github_folder("iid-ulaval", course_name, folder_path)
23
+
24
+
25
+ def _download_github_folder(
26
+ repo_owner, repo_name, folder_path, output_dir=".", branch="main"
27
+ ):
28
+ # GitHub API URL for repository contents
29
+ api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/contents/{folder_path}?ref={branch}"
30
+
31
+ response = requests.get(api_url)
32
+
33
+ # We ignore 404 since some modules don't contain a resources folder
34
+ if response.status_code == 404:
35
+ return
36
+
37
+ if response.status_code != 200:
38
+ print(
39
+ f"Error: Unable to fetch folder contents. Status code: {response.status_code}"
40
+ )
41
+ print(response.json().get("message", ""))
42
+ return
43
+
44
+ items = response.json()
45
+
46
+ # Create the local target directory if it doesn't exist
47
+ local_folder_name = os.path.basename(folder_path.strip("/"))
48
+ target_dir = os.path.join(output_dir, local_folder_name)
49
+ os.makedirs(target_dir, exist_ok=True)
50
+
51
+ for item in items:
52
+ if item["type"] == "file":
53
+ file_name = item["name"]
54
+ download_url = item["download_url"]
55
+ file_path = os.path.join(target_dir, file_name)
56
+
57
+ _download_from_url(download_url, file_name, file_path)
58
+
59
+ elif item["type"] == "dir":
60
+ # Recursively handle subfolders
61
+ subfolder_path = f"{folder_path}/{item['name']}"
62
+ _download_github_folder(
63
+ repo_owner, repo_name, subfolder_path, target_dir, branch
64
+ )
65
+
66
+ print(f"Successfully downloaded folder to: {target_dir}")
67
+
68
+
69
+ def _download_github_file(
70
+ repo_owner: str,
71
+ repo_name: str,
72
+ file_path: str,
73
+ output_dir: str = ".",
74
+ branch: str = "main",
75
+ ):
76
+ file_name = os.path.basename(file_path)
77
+
78
+ download_url = f"https://raw.githubusercontent.com/{repo_owner}/{repo_name}/{branch}/{file_path}"
79
+
80
+ file_path = os.path.join(output_dir, file_name)
81
+
82
+ _download_from_url(download_url, file_name, file_path)
83
+
84
+
85
+ def _download_from_url(download_url: str, file_name: str, file_path: str):
86
+
87
+ file_response = requests.get(download_url)
88
+
89
+ if file_response.status_code == 200:
90
+ with open(file_path, "wb") as f:
91
+ f.write(file_response.content)
92
+
93
+ print(f"Downloaded: {file_name}...")
94
+
95
+ # We ignore 404 since some modules don't contain a requirements file
96
+ elif file_response.status_code == 404:
97
+ pass
98
+ else:
99
+ print(f"Failed to download file: {file_name}")