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.
Files changed (63) hide show
  1. {peerannot-0.0.1.post41/peerannot.egg-info → peerannot-0.0.1.post43}/PKG-INFO +44 -22
  2. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/README.rst +6 -8
  3. peerannot-0.0.1.post43/VERSION.txt +1 -0
  4. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/__init__.py +25 -27
  5. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/agg_deep/CoNAL.py +187 -49
  6. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/agg_deep/Crowdlayer.py +133 -58
  7. peerannot-0.0.1.post43/peerannot/models/aggregation/dawid_skene.py +332 -0
  8. 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
  9. peerannot-0.0.1.post41/peerannot/models/aggregation/GLAD.py → peerannot-0.0.1.post43/peerannot/models/aggregation/glad.py +62 -46
  10. peerannot-0.0.1.post41/peerannot/models/aggregation/IWMV.py → peerannot-0.0.1.post43/peerannot/models/aggregation/iwmv.py +43 -19
  11. peerannot-0.0.1.post41/peerannot/models/aggregation/MV.py → peerannot-0.0.1.post43/peerannot/models/aggregation/majority_voting.py +21 -21
  12. peerannot-0.0.1.post41/peerannot/models/aggregation/NaiveSoft.py → peerannot-0.0.1.post43/peerannot/models/aggregation/naive_soft.py +13 -6
  13. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/aggregation/plantnet.py +189 -58
  14. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/aggregation/twothird.py +46 -35
  15. peerannot-0.0.1.post43/peerannot/models/aggregation/types.py +20 -0
  16. peerannot-0.0.1.post43/peerannot/models/aggregation/warnings_errors.py +41 -0
  17. peerannot-0.0.1.post43/peerannot/models/aggregation/wawa.py +67 -0
  18. peerannot-0.0.1.post41/peerannot/models/aggregation/WDS.py → peerannot-0.0.1.post43/peerannot/models/aggregation/wds.py +36 -22
  19. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/identification/AUM.py +64 -36
  20. peerannot-0.0.1.post43/peerannot/models/identification/Spam_score.py +94 -0
  21. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/identification/WAUM.py +110 -47
  22. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/identification/WAUM_perworker.py +145 -44
  23. peerannot-0.0.1.post43/peerannot/models/identification/entropy.py +55 -0
  24. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/identification/krippendorff_alpha.py +4 -1
  25. peerannot-0.0.1.post43/peerannot/models/identification/trace_confusion.py +65 -0
  26. peerannot-0.0.1.post41/peerannot/helpers/converters.py → peerannot-0.0.1.post43/peerannot/models/template.py +54 -16
  27. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/datasets.py +3 -3
  28. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/identify.py +24 -21
  29. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/run.py +44 -39
  30. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/simulate.py +9 -8
  31. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/train.py +35 -32
  32. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/training/load_data.py +6 -7
  33. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43/peerannot.egg-info}/PKG-INFO +44 -22
  34. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot.egg-info/SOURCES.txt +10 -9
  35. peerannot-0.0.1.post43/peerannot.egg-info/requires.txt +37 -0
  36. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/setup.cfg +32 -13
  37. peerannot-0.0.1.post43/setup.py +13 -0
  38. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/tests/test_filecreation.py +12 -10
  39. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/tests/test_identification.py +15 -8
  40. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/tests/test_models.py +40 -12
  41. peerannot-0.0.1.post41/VERSION.txt +0 -1
  42. peerannot-0.0.1.post41/peerannot/models/aggregation/DS.py +0 -129
  43. peerannot-0.0.1.post41/peerannot/models/aggregation/Wawa.py +0 -38
  44. peerannot-0.0.1.post41/peerannot/models/identification/Spam_score.py +0 -44
  45. peerannot-0.0.1.post41/peerannot/models/identification/entropy.py +0 -24
  46. peerannot-0.0.1.post41/peerannot/models/identification/trace_confusion.py +0 -35
  47. peerannot-0.0.1.post41/peerannot/models/template.py +0 -16
  48. peerannot-0.0.1.post41/peerannot.egg-info/requires.txt +0 -18
  49. peerannot-0.0.1.post41/setup.py +0 -7
  50. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/LICENSE +0 -0
  51. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/__init__.py +0 -0
  52. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/helpers/__init__.py +0 -0
  53. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/helpers/helpers_visu.py +0 -0
  54. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/helpers/networks.py +0 -0
  55. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/helpers/simulations_strategies.py +0 -0
  56. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/agg_deep/__init__.py +0 -0
  57. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/aggregation/__init__.py +0 -0
  58. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/models/identification/__init__.py +0 -0
  59. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/runners/__init__.py +2 -2
  60. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot/training/__init__.py +0 -0
  61. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot.egg-info/dependency_links.txt +0 -0
  62. {peerannot-0.0.1.post41 → peerannot-0.0.1.post43}/peerannot.egg-info/entry_points.txt +0 -0
  63. {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
1
+ Metadata-Version: 2.4
2
2
  Name: peerannot
3
- Version: 0.0.1.post41
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@umontpellier.fr
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.8
17
- Classifier: Programming Language :: Python :: 3.9
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: >=3.8
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>=4.12
32
- Requires-Dist: tqdm
33
- Requires-Dist: pooch
34
- Requires-Dist: Pillow
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+| |Documentation| |Codecov|
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 ["MV", "NaiveSoft", "DS", "GLAD", "WDS"]:
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 ["MV", "NaiveSoft", "DS", "GLAD", "WDS"]:
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:: default
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
- .. |Documentation| image:: https://github.com/peerannot/peerannot.github.io/actions/workflows/deploy-jekyll.yml/badge.svg
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+| |Documentation| |Codecov|
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 ["MV", "NaiveSoft", "DS", "GLAD", "WDS"]:
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 ["MV", "NaiveSoft", "DS", "GLAD", "WDS"]:
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:: default
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
- .. |Documentation| image:: https://github.com/peerannot/peerannot.github.io/actions/workflows/deploy-jekyll.yml/badge.svg
167
- :target: https://peerannot.github.io
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 .aggregation.MV import MV
2
- from .aggregation.NaiveSoft import NaiveSoft
3
- from .aggregation.DS import Dawid_Skene
4
- from .aggregation.DS_clust import Dawid_Skene_clust
5
- from .aggregation.GLAD import GLAD
6
- from .aggregation.WDS import WDS
7
- from .aggregation.twothird import TwoThird
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.Wawa import Wawa
11
- from .aggregation.IWMV import IWMV
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 .agg_deep.CoNAL import CoNAL
21
- from .agg_deep.Crowdlayer import Crowdlayer
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
- "MV": MV,
25
- "NaiveSoft": NaiveSoft,
26
- "DS": Dawid_Skene,
27
- "DSWC": Dawid_Skene_clust,
28
- "GLAD": GLAD,
29
- "WDS": WDS,
30
- "PlantNet": PlantNet,
31
- "TwoThird": TwoThird,
32
- "IWMV": IWMV,
33
- "Wawa": Wawa,
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
- from tqdm.auto import tqdm
18
- import json
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, requires_grad=True
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", f, self.global_confusion_matrix
101
+ "ij,jk->ik",
102
+ f,
103
+ self.global_confusion_matrix,
74
104
  )
75
105
  local_probs = torch.einsum(
76
- "ik,jkl->ijl", f, self.local_confusion_matrices
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, annotator_dim, embedding_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, n_annotator
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
- ) # avoid circular imports
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, "r") as ans:
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
- if kwargs.get("path_remove", None):
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, labels_path=None, **kwargs
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, optimizer, **kwargs
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
- # get correct training labels
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 = DataLoader(
201
- self.trainset,
202
- shuffle=True,
203
- batch_size=kwargs["batch_size"],
204
- num_workers=kwargs["num_workers"],
205
- pin_memory=(torch.cuda.is_available()),
206
- ), DataLoader(
207
- self.testset,
208
- batch_size=kwargs["batch_size"],
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", "w"
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: