iid-course-utils 0.1__tar.gz
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.
- iid_course_utils-0.1/.github/workflows/pypi-publish.yml +54 -0
- iid_course_utils-0.1/.gitignore +134 -0
- iid_course_utils-0.1/CHANGELOG.md +3 -0
- iid_course_utils-0.1/LICENSE +21 -0
- iid_course_utils-0.1/PKG-INFO +21 -0
- iid_course_utils-0.1/README.md +1 -0
- iid_course_utils-0.1/iidcourse/__init__.py +3 -0
- iid_course_utils-0.1/iidcourse/convolution/__init__.py +6 -0
- iid_course_utils-0.1/iidcourse/convolution/architectures.py +234 -0
- iid_course_utils-0.1/iidcourse/convolution/callbacks.py +11 -0
- iid_course_utils-0.1/iidcourse/convolution/datasets.py +91 -0
- iid_course_utils-0.1/iidcourse/convolution/visualisation.py +246 -0
- iid_course_utils-0.1/iidcourse/convolution/weights_init.py +10 -0
- iid_course_utils-0.1/iidcourse/nlp/__init__.py +4 -0
- iid_course_utils-0.1/iidcourse/nlp/data_transformer.py +104 -0
- iid_course_utils-0.1/iidcourse/nlp/datasets.py +30 -0
- iid_course_utils-0.1/iidcourse/nlp/evaluate.py +43 -0
- iid_course_utils-0.1/iidcourse/resources/__init__.py +1 -0
- iid_course_utils-0.1/iidcourse/resources/files_setup.py +99 -0
- iid_course_utils-0.1/pyproject.toml +32 -0
- iid_course_utils-0.1/requirements.txt +10 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish package to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
name: Build distribution
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v6
|
|
14
|
+
with:
|
|
15
|
+
persist-credentials: false
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v6
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.x"
|
|
20
|
+
- name: Install pypa/build
|
|
21
|
+
run: >-
|
|
22
|
+
python3 -m
|
|
23
|
+
pip install
|
|
24
|
+
build
|
|
25
|
+
--user
|
|
26
|
+
- name: Build a binary wheel and a source tarball
|
|
27
|
+
run: python3 -m build
|
|
28
|
+
- name: Store the distribution packages
|
|
29
|
+
uses: actions/upload-artifact@v5
|
|
30
|
+
with:
|
|
31
|
+
name: python-package-distributions
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
deploy:
|
|
35
|
+
name: Publish package
|
|
36
|
+
needs:
|
|
37
|
+
- build
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
environment:
|
|
40
|
+
name: pypi
|
|
41
|
+
url: https://pypi.org/p/iid-course-utils
|
|
42
|
+
permissions:
|
|
43
|
+
id-token: write
|
|
44
|
+
|
|
45
|
+
steps:
|
|
46
|
+
- name: Download all the dists
|
|
47
|
+
uses: actions/download-artifact@v6
|
|
48
|
+
with:
|
|
49
|
+
name: python-package-distributions
|
|
50
|
+
path: dist/
|
|
51
|
+
- name: Publish distribution to PyPI
|
|
52
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
53
|
+
|
|
54
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.coverage
|
|
45
|
+
.coverage.*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
*.py,cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
target/
|
|
76
|
+
|
|
77
|
+
# Jupyter Notebook
|
|
78
|
+
.ipynb_checkpoints
|
|
79
|
+
|
|
80
|
+
# IPython
|
|
81
|
+
profile_default/
|
|
82
|
+
ipython_config.py
|
|
83
|
+
|
|
84
|
+
# pyenv
|
|
85
|
+
.python-version
|
|
86
|
+
|
|
87
|
+
# pipenv
|
|
88
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
89
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
90
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
91
|
+
# install all needed dependencies.
|
|
92
|
+
#Pipfile.lock
|
|
93
|
+
|
|
94
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
95
|
+
__pypackages__/
|
|
96
|
+
|
|
97
|
+
# Celery stuff
|
|
98
|
+
celerybeat-schedule
|
|
99
|
+
celerybeat.pid
|
|
100
|
+
|
|
101
|
+
# SageMath parsed files
|
|
102
|
+
*.sage.py
|
|
103
|
+
|
|
104
|
+
# Environments
|
|
105
|
+
.env
|
|
106
|
+
.venv
|
|
107
|
+
env/
|
|
108
|
+
venv/
|
|
109
|
+
ENV/
|
|
110
|
+
env.bak/
|
|
111
|
+
venv.bak/
|
|
112
|
+
|
|
113
|
+
# Spyder project settings
|
|
114
|
+
.spyderproject
|
|
115
|
+
.spyproject
|
|
116
|
+
|
|
117
|
+
# Rope project settings
|
|
118
|
+
.ropeproject
|
|
119
|
+
|
|
120
|
+
# mkdocs documentation
|
|
121
|
+
/site
|
|
122
|
+
|
|
123
|
+
# mypy
|
|
124
|
+
.mypy_cache/
|
|
125
|
+
.dmypy.json
|
|
126
|
+
dmypy.json
|
|
127
|
+
|
|
128
|
+
# Pyre type checker
|
|
129
|
+
.pyre/
|
|
130
|
+
|
|
131
|
+
.idea/
|
|
132
|
+
.vscode/
|
|
133
|
+
|
|
134
|
+
.DS_Store
|
|
@@ -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.
|
|
@@ -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 @@
|
|
|
1
|
+
# A library for internal course tools used in IID ULaval courses
|
|
@@ -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,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}")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling >= 1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[tool.hatch.build.targets.wheel]
|
|
6
|
+
packages = ["iidcourse"]
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "iid-course-utils"
|
|
10
|
+
version = "0.1"
|
|
11
|
+
description = "A library for internal course tools used in IID ULaval courses."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "Marouane Yassine", email = "marouane.yassine.1@ulaval.ca"},
|
|
15
|
+
]
|
|
16
|
+
license = "MIT"
|
|
17
|
+
license-files = ["LICENSE"]
|
|
18
|
+
requires-python = ">= 3.8"
|
|
19
|
+
dependencies = [
|
|
20
|
+
"scikit-learn",
|
|
21
|
+
"torch",
|
|
22
|
+
"torchtext",
|
|
23
|
+
"poutyne",
|
|
24
|
+
"numpy",
|
|
25
|
+
"matplotlib",
|
|
26
|
+
"pandas",
|
|
27
|
+
"seaborn",
|
|
28
|
+
"requests"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/iid-ulaval/iid-course-utils"
|