peerannot 0.0.1.post41__tar.gz → 0.0.1.post43__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.
- {peerannot-0.0.1.post41/peerannot.egg-info → peerannot-0.0.1.post43}/PKG-INFO +44 -22
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/README.rst +6 -8
- peerannot-0.0.1.post43/VERSION.txt +1 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/__init__.py +25 -27
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/agg_deep/CoNAL.py +187 -49
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/agg_deep/Crowdlayer.py +133 -58
- peerannot-0.0.1.post43/peerannot/models/aggregation/dawid_skene.py +332 -0
- peerannot-0.0.1.post41/peerannot/models/aggregation/DS_clust.py → peerannot-0.0.1.post43/peerannot/models/aggregation/dawid_skene_clust.py +60 -34
- peerannot-0.0.1.post41/peerannot/models/aggregation/GLAD.py → peerannot-0.0.1.post43/peerannot/models/aggregation/glad.py +62 -46
- peerannot-0.0.1.post41/peerannot/models/aggregation/IWMV.py → peerannot-0.0.1.post43/peerannot/models/aggregation/iwmv.py +43 -19
- peerannot-0.0.1.post41/peerannot/models/aggregation/MV.py → peerannot-0.0.1.post43/peerannot/models/aggregation/majority_voting.py +21 -21
- peerannot-0.0.1.post41/peerannot/models/aggregation/NaiveSoft.py → peerannot-0.0.1.post43/peerannot/models/aggregation/naive_soft.py +13 -6
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/aggregation/plantnet.py +189 -58
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/aggregation/twothird.py +46 -35
- peerannot-0.0.1.post43/peerannot/models/aggregation/types.py +20 -0
- peerannot-0.0.1.post43/peerannot/models/aggregation/warnings_errors.py +41 -0
- peerannot-0.0.1.post43/peerannot/models/aggregation/wawa.py +67 -0
- peerannot-0.0.1.post41/peerannot/models/aggregation/WDS.py → peerannot-0.0.1.post43/peerannot/models/aggregation/wds.py +36 -22
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/identification/AUM.py +64 -36
- peerannot-0.0.1.post43/peerannot/models/identification/Spam_score.py +94 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/identification/WAUM.py +110 -47
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/identification/WAUM_perworker.py +145 -44
- peerannot-0.0.1.post43/peerannot/models/identification/entropy.py +55 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/identification/krippendorff_alpha.py +4 -1
- peerannot-0.0.1.post43/peerannot/models/identification/trace_confusion.py +65 -0
- peerannot-0.0.1.post41/peerannot/helpers/converters.py → peerannot-0.0.1.post43/peerannot/models/template.py +54 -16
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/datasets.py +3 -3
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/identify.py +24 -21
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/run.py +44 -39
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/simulate.py +9 -8
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/train.py +35 -32
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/training/load_data.py +6 -7
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43/peerannot.egg-info}/PKG-INFO +44 -22
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot.egg-info/SOURCES.txt +10 -9
- peerannot-0.0.1.post43/peerannot.egg-info/requires.txt +37 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/setup.cfg +32 -13
- peerannot-0.0.1.post43/setup.py +13 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/tests/test_filecreation.py +12 -10
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/tests/test_identification.py +15 -8
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/tests/test_models.py +40 -12
- peerannot-0.0.1.post41/VERSION.txt +0 -1
- peerannot-0.0.1.post41/peerannot/models/aggregation/DS.py +0 -129
- peerannot-0.0.1.post41/peerannot/models/aggregation/Wawa.py +0 -38
- peerannot-0.0.1.post41/peerannot/models/identification/Spam_score.py +0 -44
- peerannot-0.0.1.post41/peerannot/models/identification/entropy.py +0 -24
- peerannot-0.0.1.post41/peerannot/models/identification/trace_confusion.py +0 -35
- peerannot-0.0.1.post41/peerannot/models/template.py +0 -16
- peerannot-0.0.1.post41/peerannot.egg-info/requires.txt +0 -18
- peerannot-0.0.1.post41/setup.py +0 -7
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/LICENSE +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/__init__.py +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/helpers/__init__.py +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/helpers/helpers_visu.py +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/helpers/networks.py +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/helpers/simulations_strategies.py +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/agg_deep/__init__.py +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/aggregation/__init__.py +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/identification/__init__.py +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/__init__.py +2 -2
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/training/__init__.py +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot.egg-info/dependency_links.txt +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot.egg-info/entry_points.txt +0 -0
- {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot.egg-info/top_level.txt +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: peerannot
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.post43
|
|
4
4
|
Summary: Crowdsourcing library
|
|
5
5
|
Download-URL: https://github.com/peerannot/peerannot.git
|
|
6
6
|
Maintainer: Tanguy Lefort
|
|
7
|
-
Maintainer-email: tanguy.lefort@
|
|
7
|
+
Maintainer-email: tanguy.lefort@seenovate.com
|
|
8
8
|
License: BSD (3-clause)
|
|
9
9
|
Platform: any
|
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -13,36 +13,60 @@ Classifier: Intended Audience :: Developers
|
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
14
14
|
Classifier: License :: OSI Approved :: BSD License
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
19
|
Classifier: Topic :: Scientific/Engineering
|
|
19
20
|
Classifier: Topic :: Utilities
|
|
20
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
-
Requires-Python:
|
|
22
|
+
Requires-Python: <3.14,>=3.8
|
|
23
|
+
Description-Content-Type: text/x-rst
|
|
22
24
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: numpy
|
|
24
|
-
Requires-Dist: scipy
|
|
25
|
-
Requires-Dist: pandas
|
|
26
|
-
Requires-Dist: click>=8.0
|
|
25
|
+
Requires-Dist: numpy>=1.22.1
|
|
26
|
+
Requires-Dist: scipy>=1.7.3
|
|
27
|
+
Requires-Dist: pandas>=1.5.2
|
|
28
|
+
Requires-Dist: click>=8.0.0
|
|
27
29
|
Requires-Dist: torch
|
|
28
30
|
Requires-Dist: torchvision
|
|
29
31
|
Requires-Dist: torchmetrics
|
|
30
|
-
Requires-Dist: scikit-learn
|
|
31
|
-
Requires-Dist: plotly>=
|
|
32
|
-
Requires-Dist: tqdm
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist:
|
|
32
|
+
Requires-Dist: scikit-learn>=1.2.1
|
|
33
|
+
Requires-Dist: plotly>=5.10.0
|
|
34
|
+
Requires-Dist: tqdm>=4.64.0
|
|
35
|
+
Requires-Dist: cffi>=1.17.1
|
|
36
|
+
Requires-Dist: pydantic>=2.10.6
|
|
37
|
+
Requires-Dist: sparse==0.15.5
|
|
38
|
+
Requires-Dist: Pillow~=11.1.0
|
|
39
|
+
Requires-Dist: line_profiler
|
|
40
|
+
Requires-Dist: loguru
|
|
35
41
|
Provides-Extra: test
|
|
36
42
|
Requires-Dist: pytest; extra == "test"
|
|
37
43
|
Requires-Dist: pytest-cov; extra == "test"
|
|
38
44
|
Requires-Dist: pytest-timeout; extra == "test"
|
|
39
45
|
Requires-Dist: coverage; extra == "test"
|
|
46
|
+
Provides-Extra: doc
|
|
47
|
+
Requires-Dist: sphinx; extra == "doc"
|
|
48
|
+
Requires-Dist: numpydoc; extra == "doc"
|
|
49
|
+
Requires-Dist: sphinx-click; extra == "doc"
|
|
50
|
+
Requires-Dist: sphinx-nefertiti; extra == "doc"
|
|
51
|
+
Requires-Dist: sphinx-copybutton; extra == "doc"
|
|
52
|
+
Requires-Dist: sphinx-prompt; extra == "doc"
|
|
53
|
+
Requires-Dist: sphinx-gallery; extra == "doc"
|
|
54
|
+
Requires-Dist: sphinx-design; extra == "doc"
|
|
55
|
+
Requires-Dist: sphinx-book-theme; extra == "doc"
|
|
56
|
+
Requires-Dist: nbsphinx; extra == "doc"
|
|
57
|
+
Requires-Dist: notebook; extra == "doc"
|
|
58
|
+
Requires-Dist: ipython; extra == "doc"
|
|
59
|
+
Requires-Dist: matplotlib; extra == "doc"
|
|
60
|
+
Dynamic: description
|
|
61
|
+
Dynamic: description-content-type
|
|
62
|
+
Dynamic: download-url
|
|
63
|
+
Dynamic: license-file
|
|
40
64
|
|
|
41
65
|
*A Python library for managing and learning from crowdsourced labels in image classification tasks—*
|
|
42
66
|
|
|
43
67
|
----
|
|
44
68
|
|
|
45
|
-
|Pypi Status| |Python 3.8+| |
|
|
69
|
+
|Pypi Status| |Python 3.8+| |Codecov|
|
|
46
70
|
|
|
47
71
|
The ``peerannot`` library was created to handle crowdsourced labels in classification problems.
|
|
48
72
|
|
|
@@ -79,7 +103,7 @@ In python, we can run classical aggregation strategies from the current dataset
|
|
|
79
103
|
|
|
80
104
|
.. code-block:: python
|
|
81
105
|
|
|
82
|
-
for strat in ["
|
|
106
|
+
for strat in ["majority_voting", "naive_soft", "dawid_skene", "glad", "wds"]:
|
|
83
107
|
! peerannot aggregate . -s {strat}
|
|
84
108
|
|
|
85
109
|
This will create a new folder names `labels` containing the labels in the `labels_cifar10H_${strat}.npy` file.
|
|
@@ -90,7 +114,7 @@ Once the labels are available, we can train a neural network with ``PyTorch`` as
|
|
|
90
114
|
|
|
91
115
|
.. code-block:: python
|
|
92
116
|
|
|
93
|
-
for strat in ["
|
|
117
|
+
for strat in ["majority_voting", "naive_soft", "dawid_skene", "glad", "wds"]:
|
|
94
118
|
! peerannot train . -o cifar10H_${strat} \
|
|
95
119
|
-K 10 \
|
|
96
120
|
--labels=./labels/labels_cifar-10h_${strat}.npy \
|
|
@@ -125,7 +149,7 @@ In ``peerannot``, one of our goals is to make crowdsourced datasets under the sa
|
|
|
125
149
|
|
|
126
150
|
So, what is a crowdsourced dataset? We define each dataset as:
|
|
127
151
|
|
|
128
|
-
.. code-block::
|
|
152
|
+
.. code-block:: bash
|
|
129
153
|
|
|
130
154
|
dataset
|
|
131
155
|
├── train
|
|
@@ -203,7 +227,5 @@ The ``dataset.py`` is not mandatory but is here to facilitate the dataset's inst
|
|
|
203
227
|
:target: https://github.com/peerannot/peerannot/actions/workflows/python-publish.yml
|
|
204
228
|
.. |Python 3.8+| image:: https://github.com/peerannot/peerannot/actions/workflows/pytest.yml/badge.svg
|
|
205
229
|
:target: https://github.com/peerannot/peerannot/actions/workflows/pytest.yml
|
|
206
|
-
.. |
|
|
207
|
-
:target: https://peerannot.github.io
|
|
208
|
-
.. |Codecov| image:: https://codecov.io/gh/peerannot/peerannot/graph/badge.svg?token=3U77QPSODB
|
|
230
|
+
.. |Codecov| image:: https://codecov.io/gh/peerannot/peerannot/graph/badge.svg?token=3U77QPSODB
|
|
209
231
|
:target: https://codecov.io/gh/peerannot/peerannot
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
----
|
|
4
4
|
|
|
5
|
-
|Pypi Status| |Python 3.8+| |
|
|
5
|
+
|Pypi Status| |Python 3.8+| |Codecov|
|
|
6
6
|
|
|
7
7
|
The ``peerannot`` library was created to handle crowdsourced labels in classification problems.
|
|
8
8
|
|
|
@@ -39,7 +39,7 @@ In python, we can run classical aggregation strategies from the current dataset
|
|
|
39
39
|
|
|
40
40
|
.. code-block:: python
|
|
41
41
|
|
|
42
|
-
for strat in ["
|
|
42
|
+
for strat in ["majority_voting", "naive_soft", "dawid_skene", "glad", "wds"]:
|
|
43
43
|
! peerannot aggregate . -s {strat}
|
|
44
44
|
|
|
45
45
|
This will create a new folder names `labels` containing the labels in the `labels_cifar10H_${strat}.npy` file.
|
|
@@ -50,7 +50,7 @@ Once the labels are available, we can train a neural network with ``PyTorch`` as
|
|
|
50
50
|
|
|
51
51
|
.. code-block:: python
|
|
52
52
|
|
|
53
|
-
for strat in ["
|
|
53
|
+
for strat in ["majority_voting", "naive_soft", "dawid_skene", "glad", "wds"]:
|
|
54
54
|
! peerannot train . -o cifar10H_${strat} \
|
|
55
55
|
-K 10 \
|
|
56
56
|
--labels=./labels/labels_cifar-10h_${strat}.npy \
|
|
@@ -85,7 +85,7 @@ In ``peerannot``, one of our goals is to make crowdsourced datasets under the sa
|
|
|
85
85
|
|
|
86
86
|
So, what is a crowdsourced dataset? We define each dataset as:
|
|
87
87
|
|
|
88
|
-
.. code-block::
|
|
88
|
+
.. code-block:: bash
|
|
89
89
|
|
|
90
90
|
dataset
|
|
91
91
|
├── train
|
|
@@ -163,7 +163,5 @@ The ``dataset.py`` is not mandatory but is here to facilitate the dataset's inst
|
|
|
163
163
|
:target: https://github.com/peerannot/peerannot/actions/workflows/python-publish.yml
|
|
164
164
|
.. |Python 3.8+| image:: https://github.com/peerannot/peerannot/actions/workflows/pytest.yml/badge.svg
|
|
165
165
|
:target: https://github.com/peerannot/peerannot/actions/workflows/pytest.yml
|
|
166
|
-
.. |
|
|
167
|
-
:target: https://
|
|
168
|
-
.. |Codecov| image:: https://codecov.io/gh/peerannot/peerannot/graph/badge.svg?token=3U77QPSODB
|
|
169
|
-
:target: https://codecov.io/gh/peerannot/peerannot
|
|
166
|
+
.. |Codecov| image:: https://codecov.io/gh/peerannot/peerannot/graph/badge.svg?token=3U77QPSODB
|
|
167
|
+
:target: https://codecov.io/gh/peerannot/peerannot
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.1-43
|
|
@@ -1,36 +1,34 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
3
|
-
from .aggregation.
|
|
4
|
-
from .aggregation.
|
|
5
|
-
from .aggregation.
|
|
6
|
-
from .aggregation.
|
|
7
|
-
from .aggregation.
|
|
1
|
+
from .agg_deep.CoNAL import CoNAL
|
|
2
|
+
from .agg_deep.Crowdlayer import Crowdlayer
|
|
3
|
+
from .aggregation.dawid_skene import DawidSkene
|
|
4
|
+
from .aggregation.dawid_skene_clust import DawidSkeneClust
|
|
5
|
+
from .aggregation.glad import GLAD
|
|
6
|
+
from .aggregation.iwmv import IWMV
|
|
7
|
+
from .aggregation.majority_voting import MajorityVoting
|
|
8
|
+
from .aggregation.naive_soft import NaiveSoft
|
|
8
9
|
from .aggregation.plantnet import PlantNet
|
|
9
|
-
|
|
10
|
-
from .aggregation.
|
|
11
|
-
from .aggregation.
|
|
12
|
-
|
|
13
|
-
from .identification.WAUM_perworker import WAUM_perworker
|
|
14
|
-
from .identification.WAUM import WAUM
|
|
10
|
+
from .aggregation.twothird import TwoThird
|
|
11
|
+
from .aggregation.wawa import Wawa
|
|
12
|
+
from .aggregation.wds import WDS
|
|
15
13
|
from .identification.AUM import AUM
|
|
16
|
-
from .identification.trace_confusion import Trace_confusion
|
|
17
|
-
from .identification.Spam_score import Spam_Score
|
|
18
14
|
from .identification.entropy import Entropy
|
|
19
15
|
from .identification.krippendorff_alpha import Krippendorff_Alpha
|
|
20
|
-
from .
|
|
21
|
-
from .
|
|
16
|
+
from .identification.Spam_score import Spam_Score
|
|
17
|
+
from .identification.trace_confusion import Trace_confusion
|
|
18
|
+
from .identification.WAUM import WAUM
|
|
19
|
+
from .identification.WAUM_perworker import WAUM_perworker
|
|
22
20
|
|
|
23
21
|
agg_strategies = {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
22
|
+
"majority_voting": MajorityVoting,
|
|
23
|
+
"naive_soft": NaiveSoft,
|
|
24
|
+
"dawid_skene": DawidSkene,
|
|
25
|
+
"dawid_skene_clust": DawidSkeneClust,
|
|
26
|
+
"glad": GLAD,
|
|
27
|
+
"wds": WDS,
|
|
28
|
+
"plantnet": PlantNet,
|
|
29
|
+
"twothird": TwoThird,
|
|
30
|
+
"iwmv": IWMV,
|
|
31
|
+
"wawa": Wawa,
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
agg_deep_strategies = {
|
|
@@ -1,30 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
=====================================================
|
|
3
|
-
CoNAL (Common Noise Adaptation Layer), Chu et.al 2021
|
|
4
|
-
=====================================================
|
|
5
|
-
Implementation based from the unofficial repository
|
|
6
|
-
https://github.com/seunghyukcho/CoNAL-pytorch
|
|
7
|
-
"""
|
|
8
|
-
import torch
|
|
9
|
-
from torch import nn
|
|
10
|
-
import numpy as np
|
|
11
|
-
from torchvision import transforms
|
|
12
|
-
import torch.nn.functional as F
|
|
13
|
-
import torch.nn as nn
|
|
1
|
+
import json
|
|
14
2
|
from collections.abc import Iterable
|
|
15
|
-
from ..template import CrowdModel
|
|
16
3
|
from pathlib import Path
|
|
17
|
-
|
|
18
|
-
import
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
19
6
|
import torch
|
|
20
|
-
import torch.nn as nn
|
|
21
7
|
import torch.nn.functional as f
|
|
8
|
+
from torch import nn
|
|
22
9
|
from torch.utils.data import DataLoader
|
|
10
|
+
from tqdm.auto import tqdm
|
|
11
|
+
|
|
12
|
+
from ..template import CrowdModel
|
|
23
13
|
|
|
24
14
|
DEVICE = "cpu" if not torch.cuda.is_available() else "cuda"
|
|
25
15
|
|
|
26
16
|
|
|
27
17
|
def reformat_labels(votes, n_workers):
|
|
18
|
+
"""Convert votes to numpy array of shape (n_tasks, n_workers)
|
|
19
|
+
|
|
20
|
+
:param votes: Json dictionary with votes
|
|
21
|
+
:type votes: dict
|
|
22
|
+
:param n_workers: Number of workers
|
|
23
|
+
:type n_workers: int
|
|
24
|
+
:return: Numpy array of shape (n_tasks, n_workers), -1 when no vote
|
|
25
|
+
:rtype: np.ndarray
|
|
26
|
+
"""
|
|
28
27
|
answers = []
|
|
29
28
|
for task, ans in votes.items():
|
|
30
29
|
answers.append([-1] * n_workers)
|
|
@@ -34,7 +33,22 @@ def reformat_labels(votes, n_workers):
|
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
class AuxiliaryNetwork(nn.Module):
|
|
36
|
+
"""
|
|
37
|
+
=====================================================
|
|
38
|
+
Auxiliary Network in CoNAL architecture
|
|
39
|
+
=====================================================
|
|
40
|
+
"""
|
|
41
|
+
|
|
37
42
|
def __init__(self, x_dim, e_dim, w_dim):
|
|
43
|
+
"""Parameter structure of the Auxiliary Network
|
|
44
|
+
|
|
45
|
+
:param x_dim: Input dimension
|
|
46
|
+
:type x_dim: int
|
|
47
|
+
:param e_dim: Worker metadata input dimension
|
|
48
|
+
:type e_dim: int
|
|
49
|
+
:param w_dim: Dimension of the embedding space
|
|
50
|
+
:type w_dim: int
|
|
51
|
+
"""
|
|
38
52
|
super().__init__()
|
|
39
53
|
|
|
40
54
|
self.weight_v1 = nn.Linear(x_dim, 128)
|
|
@@ -55,25 +69,43 @@ class AuxiliaryNetwork(nn.Module):
|
|
|
55
69
|
|
|
56
70
|
|
|
57
71
|
class NoiseAdaptationLayer(nn.Module):
|
|
72
|
+
"""
|
|
73
|
+
=====================================================
|
|
74
|
+
Model global and local confusions in the architecture
|
|
75
|
+
=====================================================
|
|
76
|
+
"""
|
|
77
|
+
|
|
58
78
|
def __init__(self, n_class, n_annotator):
|
|
79
|
+
"""Instantiate the local and global confusion matrices
|
|
80
|
+
|
|
81
|
+
:param n_class: Number of classes
|
|
82
|
+
:type n_class: int
|
|
83
|
+
:param n_annotator: Number of workers
|
|
84
|
+
:type n_annotator: int
|
|
85
|
+
"""
|
|
59
86
|
super().__init__()
|
|
60
87
|
|
|
61
88
|
self.global_confusion_matrix = nn.Parameter(
|
|
62
|
-
torch.eye(n_class, n_class) * 2,
|
|
89
|
+
torch.eye(n_class, n_class) * 2,
|
|
90
|
+
requires_grad=True,
|
|
63
91
|
)
|
|
64
92
|
self.local_confusion_matrices = nn.Parameter(
|
|
65
93
|
torch.stack(
|
|
66
|
-
[torch.eye(n_class, n_class) * 2 for _ in range(n_annotator)]
|
|
94
|
+
[torch.eye(n_class, n_class) * 2 for _ in range(n_annotator)],
|
|
67
95
|
),
|
|
68
96
|
requires_grad=True,
|
|
69
97
|
)
|
|
70
98
|
|
|
71
99
|
def forward(self, f, w):
|
|
72
100
|
global_prob = torch.einsum(
|
|
73
|
-
"ij,jk->ik",
|
|
101
|
+
"ij,jk->ik",
|
|
102
|
+
f,
|
|
103
|
+
self.global_confusion_matrix,
|
|
74
104
|
)
|
|
75
105
|
local_probs = torch.einsum(
|
|
76
|
-
"ik,jkl->ijl",
|
|
106
|
+
"ik,jkl->ijl",
|
|
107
|
+
f,
|
|
108
|
+
self.local_confusion_matrices,
|
|
77
109
|
)
|
|
78
110
|
|
|
79
111
|
h = (
|
|
@@ -85,6 +117,12 @@ class NoiseAdaptationLayer(nn.Module):
|
|
|
85
117
|
|
|
86
118
|
|
|
87
119
|
class CoNAL_net(nn.Module):
|
|
120
|
+
"""
|
|
121
|
+
=====================================================
|
|
122
|
+
Neural network classifier architecture for CoNAL
|
|
123
|
+
=====================================================
|
|
124
|
+
"""
|
|
125
|
+
|
|
88
126
|
def __init__(
|
|
89
127
|
self,
|
|
90
128
|
input_dim,
|
|
@@ -94,17 +132,52 @@ class CoNAL_net(nn.Module):
|
|
|
94
132
|
annotator_dim,
|
|
95
133
|
embedding_dim,
|
|
96
134
|
):
|
|
135
|
+
"""Architecture for the CoNAL network
|
|
136
|
+
|
|
137
|
+
:param input_dim: Image input dimension
|
|
138
|
+
:type input_dim: int
|
|
139
|
+
:param n_class: Number of classes
|
|
140
|
+
:type n_class: int
|
|
141
|
+
:param n_annotator: Number of workers
|
|
142
|
+
:type n_annotator: int
|
|
143
|
+
:param classifier: Neural network classifier backbone
|
|
144
|
+
:type classifier: nn.Module
|
|
145
|
+
:param annotator_dim: Worker metadata input dimension
|
|
146
|
+
:type annotator_dim: int
|
|
147
|
+
:param embedding_dim: Dimension of the embedding space
|
|
148
|
+
:type embedding_dim: int
|
|
149
|
+
"""
|
|
97
150
|
super().__init__()
|
|
98
151
|
|
|
99
152
|
self.auxiliary_network = AuxiliaryNetwork(
|
|
100
|
-
input_dim,
|
|
153
|
+
input_dim,
|
|
154
|
+
annotator_dim,
|
|
155
|
+
embedding_dim,
|
|
101
156
|
)
|
|
102
157
|
self.classifier = classifier
|
|
103
158
|
self.noise_adaptation_layer = NoiseAdaptationLayer(
|
|
104
|
-
n_class,
|
|
159
|
+
n_class,
|
|
160
|
+
n_annotator,
|
|
105
161
|
)
|
|
106
162
|
|
|
107
163
|
def forward(self, x, annotator=None):
|
|
164
|
+
"""If no worker is associated (test phase), returns the backbone prediction.
|
|
165
|
+
|
|
166
|
+
During training, given the classifier :math:`\\mathcal{C}` with output scores :math:`z_i`, local confusions :math:`\\pi^{(j)}` and global confusion :math:`\\pi^g`, the model computes the following:
|
|
167
|
+
|
|
168
|
+
.. math::
|
|
169
|
+
|
|
170
|
+
h_i^{(j)} = \\sigma((\\omega_{i}^{(j)}\\pi^g + (1-\\omega_i^{(j)})\\pi^{(j)} ) z_i ),
|
|
171
|
+
|
|
172
|
+
with :math:`\\omega_i^{(j)}=(1+\\exp(-u_j^\\top v_i))^{-1}` where :math:`u_j` is the worker-related embedding and :math:`v_i` the task-related embedding.
|
|
173
|
+
|
|
174
|
+
:param x: Image input
|
|
175
|
+
:type x: torch.Tensor
|
|
176
|
+
:param annotator: Annotator metadata, defaults to None
|
|
177
|
+
:type annotator: torch.Tensor, optional
|
|
178
|
+
:return: Model prediction and backbone prediction
|
|
179
|
+
:rtype: tuple(torch.Tensor, torch.Tensor) or torch.Tensor (test)
|
|
180
|
+
"""
|
|
108
181
|
f = self.classifier(x)
|
|
109
182
|
if annotator is None:
|
|
110
183
|
return f
|
|
@@ -117,6 +190,14 @@ class CoNAL_net(nn.Module):
|
|
|
117
190
|
|
|
118
191
|
|
|
119
192
|
class CoNAL(CrowdModel):
|
|
193
|
+
"""
|
|
194
|
+
=====================================================
|
|
195
|
+
CoNAL (Common Noise Adaptation Layer), Chu et.al 2021
|
|
196
|
+
=====================================================
|
|
197
|
+
Implementation based from the unofficial repository
|
|
198
|
+
https://github.com/seunghyukcho/CoNAL-pytorch
|
|
199
|
+
"""
|
|
200
|
+
|
|
120
201
|
def __init__(
|
|
121
202
|
self,
|
|
122
203
|
tasks_path,
|
|
@@ -131,31 +212,75 @@ class CoNAL(CrowdModel):
|
|
|
131
212
|
output_name="conal",
|
|
132
213
|
**kwargs,
|
|
133
214
|
):
|
|
215
|
+
"""CoNAL deep learning strategy.
|
|
216
|
+
Learn a classifier with crowdsourced labels by modeling worker-specific and global confusions.
|
|
217
|
+
|
|
218
|
+
During training, given the classifier :math:`\\mathcal{C}` with output scores :math:`z_i`, local confusions :math:`\\pi^{(j)}` and global confusion :math:`\\pi^g`, the model computes the following:
|
|
219
|
+
|
|
220
|
+
.. math::
|
|
221
|
+
|
|
222
|
+
h_i^{(j)} = \\sigma((\\omega_{i}^{(j)}\\pi^g + (1-\\omega_i^{(j)})\\pi^{(j)} ) z_i ),
|
|
223
|
+
|
|
224
|
+
with :math:`\\omega_i^{(j)}=(1+\\exp(-u_j^\\top v_i))^{-1}` where :math:`u_j` is the worker-related embedding and :math:`v_i` the task-related embedding. This is computed in the `AuxiliaryNetwork`.
|
|
225
|
+
Then, the `NoiseAdaptationLayer` computes the final prediction :math:`h_i^{(j)}`.
|
|
226
|
+
|
|
227
|
+
The final loss is the crossentropy between the worker-specific prediction and the given label with a regularization term of penalty scale :math:`\\lambda>0` on the difference between the local and global confusions.
|
|
228
|
+
|
|
229
|
+
.. math::
|
|
230
|
+
|
|
231
|
+
\\mathcal{L} = \\frac{1}{n_{\\texttt{task}}}\\sum_{i=1}^{n_{\\texttt{task}}}\\sum_{j\\in\\mathcal{A}(x_i)} \\mathrm{CE}(h_i^{(j)}, y_i^{(j)}) - \\lambda \\sum_{j=1}^{n_{\\texttt{worker}}} \\|\\pi^{(j)} - \\pi^g\\|_2.
|
|
232
|
+
|
|
233
|
+
:param tasks_path: Path to images to train from
|
|
234
|
+
:type tasks_path: path
|
|
235
|
+
:param answers: Path to answers (json format)
|
|
236
|
+
|
|
237
|
+
.. code-block:: javascript
|
|
238
|
+
|
|
239
|
+
{
|
|
240
|
+
task0: {worker0: label, worker1: label},
|
|
241
|
+
task1: {worker1: label}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
:type answers: path
|
|
245
|
+
:param model: Backbone classifier architecture: should be from `torchvision.models`
|
|
246
|
+
:type model: string
|
|
247
|
+
:param n_classes: Number of classes
|
|
248
|
+
:type n_classes: int
|
|
249
|
+
:param optimizer: Pytorch optimizer name (either sgd or Adam)
|
|
250
|
+
:type optimizer: string
|
|
251
|
+
:param n_epochs: Number of training epochs
|
|
252
|
+
:type n_epochs: int
|
|
253
|
+
:param scale: Regularization parameter in the loss, defaults to 1e-5
|
|
254
|
+
:type scale: float
|
|
255
|
+
:param verbose: Verbosity level, defaults to True
|
|
256
|
+
:type verbose: bool, optional
|
|
257
|
+
:param pretrained: Use the pretrained version of the backbone classifier, defaults to False
|
|
258
|
+
:type pretrained: bool, optional
|
|
259
|
+
:param output_name: Generated file prefix, defaults to "conal"
|
|
260
|
+
:type output_name: str, optional
|
|
261
|
+
|
|
262
|
+
The batch size, learning rate, scheduler and milestones can be specified as keyword arguments.
|
|
263
|
+
Visit the `computo paper <https://computo.sfds.asso.fr/published-202402-lefort-peerannot/>`__ or the tutorial for examples.
|
|
264
|
+
"""
|
|
134
265
|
from peerannot.runners.train import (
|
|
135
266
|
get_model,
|
|
136
267
|
get_optimizer,
|
|
137
268
|
load_all_data,
|
|
138
|
-
)
|
|
269
|
+
)
|
|
139
270
|
|
|
140
271
|
self.scale = scale
|
|
141
272
|
self.tasks_path = Path(tasks_path).resolve()
|
|
142
273
|
self.answers = Path(answers).resolve()
|
|
143
|
-
with open(self.answers
|
|
274
|
+
with open(self.answers) as ans:
|
|
144
275
|
self.answers = json.load(ans)
|
|
145
276
|
super().__init__(self.answers)
|
|
146
277
|
self.answers_orig = self.answers
|
|
147
|
-
|
|
148
|
-
to_remove = np.loadtxt(kwargs["path_remove"], dtype=int)
|
|
149
|
-
self.answers_modif = {}
|
|
150
|
-
i = 0
|
|
151
|
-
for key, val in self.answers.items():
|
|
152
|
-
if int(key) not in to_remove[:, 1]:
|
|
153
|
-
self.answers_modif[i] = val
|
|
154
|
-
i += 1
|
|
155
|
-
self.answers = self.answers_modif
|
|
278
|
+
|
|
156
279
|
kwargs["labels"] = None # to prevent any loading of labels
|
|
157
280
|
self.trainset, self.valset, self.testset = load_all_data(
|
|
158
|
-
self.tasks_path,
|
|
281
|
+
self.tasks_path,
|
|
282
|
+
labels_path=None,
|
|
283
|
+
**kwargs,
|
|
159
284
|
)
|
|
160
285
|
self.input_dim = np.prod(self.trainset[0][0].shape).item()
|
|
161
286
|
self.model = get_model(
|
|
@@ -178,14 +303,16 @@ class CoNAL(CrowdModel):
|
|
|
178
303
|
embedding_dim=20,
|
|
179
304
|
)
|
|
180
305
|
self.optimizer, self.scheduler = get_optimizer(
|
|
181
|
-
self.conal_net,
|
|
306
|
+
self.conal_net,
|
|
307
|
+
optimizer,
|
|
308
|
+
**kwargs,
|
|
182
309
|
)
|
|
183
310
|
self.output_name = output_name
|
|
184
311
|
self.criterion = nn.CrossEntropyLoss(ignore_index=-1, reduction="mean")
|
|
185
312
|
self.setup(**kwargs)
|
|
186
313
|
|
|
187
314
|
def setup(self, **kwargs):
|
|
188
|
-
|
|
315
|
+
"""Create training, validation and test dataloaders from the dataset."""
|
|
189
316
|
targets, ll = [], []
|
|
190
317
|
print(len(self.answers), len(self.trainset.samples))
|
|
191
318
|
self.numpyans = reformat_labels(self.answers_orig, self.n_workers)
|
|
@@ -197,15 +324,18 @@ class CoNAL(CrowdModel):
|
|
|
197
324
|
self.trainset.samples = ll
|
|
198
325
|
self.trainset.targets = targets
|
|
199
326
|
|
|
200
|
-
self.trainloader, self.testloader =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
327
|
+
self.trainloader, self.testloader = (
|
|
328
|
+
DataLoader(
|
|
329
|
+
self.trainset,
|
|
330
|
+
shuffle=True,
|
|
331
|
+
batch_size=kwargs["batch_size"],
|
|
332
|
+
num_workers=kwargs["num_workers"],
|
|
333
|
+
pin_memory=(torch.cuda.is_available()),
|
|
334
|
+
),
|
|
335
|
+
DataLoader(
|
|
336
|
+
self.testset,
|
|
337
|
+
batch_size=kwargs["batch_size"],
|
|
338
|
+
),
|
|
209
339
|
)
|
|
210
340
|
print(f"Train set: {len(self.trainloader.dataset)} tasks")
|
|
211
341
|
print(f"Test set: {len(self.testloader.dataset)} tasks")
|
|
@@ -216,6 +346,12 @@ class CoNAL(CrowdModel):
|
|
|
216
346
|
print(f"Validation set: {len(self.valloader.dataset)} tasks")
|
|
217
347
|
|
|
218
348
|
def run(self, **kwargs):
|
|
349
|
+
"""Train the CoNAL model and evaluate on the test set.
|
|
350
|
+
|
|
351
|
+
Uses a gpu by default is `torch.cuda.is_available() is True`.
|
|
352
|
+
|
|
353
|
+
Results are stored in `<tasks_path>/results/<output_name>.json` and the best model in `<tasks_path>/best_models/<output_name>.pth`. Results contain the train, validation and test loss as well as the validation and test accuracy.
|
|
354
|
+
"""
|
|
219
355
|
from peerannot.runners.train import evaluate
|
|
220
356
|
|
|
221
357
|
print(f"Running on {DEVICE}")
|
|
@@ -271,7 +407,7 @@ class CoNAL(CrowdModel):
|
|
|
271
407
|
if epoch in kwargs["milestones"]:
|
|
272
408
|
print()
|
|
273
409
|
print(
|
|
274
|
-
f"Adjusting learning rate to = {self.scheduler.optimizer.param_groups[0]['lr']:.4f}"
|
|
410
|
+
f"Adjusting learning rate to = {self.scheduler.optimizer.param_groups[0]['lr']:.4f}",
|
|
275
411
|
)
|
|
276
412
|
|
|
277
413
|
# load and test self.conal_net
|
|
@@ -296,14 +432,16 @@ class CoNAL(CrowdModel):
|
|
|
296
432
|
print(f"- {k}: {vprint}")
|
|
297
433
|
(self.tasks_path / "results").mkdir(parents=True, exist_ok=True)
|
|
298
434
|
with open(
|
|
299
|
-
self.tasks_path / "results" / f"{self.output_name}.json",
|
|
435
|
+
self.tasks_path / "results" / f"{self.output_name}.json",
|
|
436
|
+
"w",
|
|
300
437
|
) as f:
|
|
301
438
|
json.dump(logger, f, indent=3, ensure_ascii=False)
|
|
302
439
|
print(
|
|
303
|
-
f"Results stored in {self.tasks_path / 'results' / f'{self.output_name}.json'}"
|
|
440
|
+
f"Results stored in {self.tasks_path / 'results' / f'{self.output_name}.json'}",
|
|
304
441
|
)
|
|
305
442
|
|
|
306
443
|
def run_epoch(self, model, trainloader, criterion, optimizer, logger):
|
|
444
|
+
"""Run one epoch and monitor metrics"""
|
|
307
445
|
model.train()
|
|
308
446
|
total_loss = 0.0
|
|
309
447
|
for inputs, labels in trainloader:
|