indico_toolkit 6.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- indico_toolkit/__init__.py +24 -0
- indico_toolkit/association/__init__.py +15 -0
- indico_toolkit/association/association.py +65 -0
- indico_toolkit/association/extracted_tokens.py +106 -0
- indico_toolkit/association/line_items.py +189 -0
- indico_toolkit/association/positioning.py +301 -0
- indico_toolkit/association/splitting.py +41 -0
- indico_toolkit/auto_populate/__init__.py +3 -0
- indico_toolkit/auto_populate/populator.py +362 -0
- indico_toolkit/auto_populate/types.py +66 -0
- indico_toolkit/auto_review/__init__.py +6 -0
- indico_toolkit/auto_review/auto_review_functions.py +123 -0
- indico_toolkit/auto_review/auto_reviewer.py +62 -0
- indico_toolkit/client.py +35 -0
- indico_toolkit/errors.py +32 -0
- indico_toolkit/etloutput/__init__.py +210 -0
- indico_toolkit/etloutput/cell.py +47 -0
- indico_toolkit/etloutput/errors.py +16 -0
- indico_toolkit/etloutput/etloutput.py +110 -0
- indico_toolkit/etloutput/range.py +30 -0
- indico_toolkit/etloutput/table.py +44 -0
- indico_toolkit/etloutput/token.py +25 -0
- indico_toolkit/indico_wrapper/__init__.py +15 -0
- indico_toolkit/indico_wrapper/dataset.py +114 -0
- indico_toolkit/indico_wrapper/doc_extraction.py +79 -0
- indico_toolkit/indico_wrapper/download.py +193 -0
- indico_toolkit/indico_wrapper/indico_wrapper.py +119 -0
- indico_toolkit/indico_wrapper/modelop.py +200 -0
- indico_toolkit/indico_wrapper/reviewer.py +111 -0
- indico_toolkit/indico_wrapper/workflow.py +231 -0
- indico_toolkit/metrics/__init__.py +11 -0
- indico_toolkit/metrics/compare_ground_truth.py +123 -0
- indico_toolkit/metrics/compare_models.py +180 -0
- indico_toolkit/metrics/metrics.py +373 -0
- indico_toolkit/metrics/plotting.py +102 -0
- indico_toolkit/ocr/__init__.py +9 -0
- indico_toolkit/ocr/customocr_object.py +42 -0
- indico_toolkit/ocr/ondoc_object.py +93 -0
- indico_toolkit/ocr/standard_object.py +54 -0
- indico_toolkit/pipelines/__init__.py +3 -0
- indico_toolkit/pipelines/file_processing.py +157 -0
- indico_toolkit/polling/__init__.py +8 -0
- indico_toolkit/polling/autoreview.py +204 -0
- indico_toolkit/polling/downstream.py +145 -0
- indico_toolkit/polling/queries.py +68 -0
- indico_toolkit/py.typed +0 -0
- indico_toolkit/results/__init__.py +104 -0
- indico_toolkit/results/document.py +78 -0
- indico_toolkit/results/errors.py +4 -0
- indico_toolkit/results/model.py +60 -0
- indico_toolkit/results/normalization.py +159 -0
- indico_toolkit/results/predictionlist.py +395 -0
- indico_toolkit/results/predictions/__init__.py +88 -0
- indico_toolkit/results/predictions/box.py +58 -0
- indico_toolkit/results/predictions/citation.py +47 -0
- indico_toolkit/results/predictions/classification.py +50 -0
- indico_toolkit/results/predictions/documentextraction.py +160 -0
- indico_toolkit/results/predictions/extraction.py +35 -0
- indico_toolkit/results/predictions/formextraction.py +121 -0
- indico_toolkit/results/predictions/group.py +29 -0
- indico_toolkit/results/predictions/prediction.py +41 -0
- indico_toolkit/results/predictions/span.py +43 -0
- indico_toolkit/results/predictions/summarization.py +103 -0
- indico_toolkit/results/predictions/unbundling.py +51 -0
- indico_toolkit/results/result.py +152 -0
- indico_toolkit/results/review.py +32 -0
- indico_toolkit/results/utilities.py +73 -0
- indico_toolkit/retry.py +94 -0
- indico_toolkit/snapshots/__init__.py +3 -0
- indico_toolkit/snapshots/snapshot.py +340 -0
- indico_toolkit/structure/__init__.py +3 -0
- indico_toolkit/structure/create_structure.py +232 -0
- indico_toolkit/structure/queries.py +119 -0
- indico_toolkit/structure/utils.py +16 -0
- indico_toolkit/types/__init__.py +12 -0
- indico_toolkit/types/classification.py +92 -0
- indico_toolkit/types/extractions.py +227 -0
- indico_toolkit/types/predictions.py +28 -0
- indico_toolkit/types/workflow_object.py +133 -0
- indico_toolkit-6.14.0.dist-info/LICENSE +21 -0
- indico_toolkit-6.14.0.dist-info/METADATA +144 -0
- indico_toolkit-6.14.0.dist-info/RECORD +83 -0
- indico_toolkit-6.14.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from io import StringIO
|
|
3
|
+
|
|
4
|
+
from indico import IndicoClient, IndicoRequestError
|
|
5
|
+
from indico.queries import (
|
|
6
|
+
CreateExport,
|
|
7
|
+
DownloadExport,
|
|
8
|
+
GraphQLRequest,
|
|
9
|
+
RetrieveStorageObject,
|
|
10
|
+
)
|
|
11
|
+
from indico.types.export import Export
|
|
12
|
+
|
|
13
|
+
from ..errors import ToolkitInputError
|
|
14
|
+
from ..retry import retry
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import pandas as pd
|
|
18
|
+
|
|
19
|
+
_PANDAS_INSTALLED = True
|
|
20
|
+
except ImportError as error:
|
|
21
|
+
_PANDAS_INSTALLED = False
|
|
22
|
+
_IMPORT_ERROR = error
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Download:
|
|
26
|
+
"""
|
|
27
|
+
Class to support downloading resources from an Indico Cluster
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, client: IndicoClient):
|
|
31
|
+
self.client = client
|
|
32
|
+
|
|
33
|
+
def get_dataset_pdfs(
|
|
34
|
+
self,
|
|
35
|
+
dataset_id: int,
|
|
36
|
+
labelset_id: int,
|
|
37
|
+
output_dir: str,
|
|
38
|
+
max_files_to_download: int = None,
|
|
39
|
+
) -> int:
|
|
40
|
+
"""Download PDFs from an uploaded dataset to a local directory
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
dataset_id (int): Dataset ID to download from
|
|
44
|
+
labelset_id (int): ID of your labelset (from teach task)
|
|
45
|
+
output_dir (str): Path to directory to write PDFs
|
|
46
|
+
max_files_to_download (int): = Max number of files to download
|
|
47
|
+
(default: None = download all)
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ToolkitInputError: Exception if invalid directory path
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
int: Number of files downloaded
|
|
54
|
+
"""
|
|
55
|
+
if not os.path.isdir(output_dir):
|
|
56
|
+
raise ToolkitInputError(f"Path is not a directory: {output_dir}")
|
|
57
|
+
export = self._create_export(dataset_id, labelset_id)
|
|
58
|
+
df = self._download_export(export.id)
|
|
59
|
+
num_files_downloaded = self._download_pdfs_from_export(
|
|
60
|
+
df,
|
|
61
|
+
output_dir,
|
|
62
|
+
f"file_name_{dataset_id}",
|
|
63
|
+
f"file_url_{dataset_id}",
|
|
64
|
+
max_files_to_download,
|
|
65
|
+
)
|
|
66
|
+
return num_files_downloaded
|
|
67
|
+
|
|
68
|
+
def get_uploaded_csv_dataframe(self, dataset_id: int) -> "pd.DataFrame":
|
|
69
|
+
"""
|
|
70
|
+
Get a dataframe from a CSV that has been uploaded to the platform
|
|
71
|
+
Args:
|
|
72
|
+
dataset_id (int): id of the dataset
|
|
73
|
+
Returns:
|
|
74
|
+
pd.DataFrame: a dataframe representation of the CSV you uploaded
|
|
75
|
+
"""
|
|
76
|
+
if not _PANDAS_INSTALLED:
|
|
77
|
+
raise RuntimeError(
|
|
78
|
+
"getting an uploaded CSV dataframe requires additional dependencies: "
|
|
79
|
+
"`pip install indico-toolkit[downloads]`"
|
|
80
|
+
) from _IMPORT_ERROR
|
|
81
|
+
|
|
82
|
+
url = self._get_csv_download_url(dataset_id)
|
|
83
|
+
string_df = self._retrieve_storage_object(url)
|
|
84
|
+
return pd.read_csv(StringIO(string_df))
|
|
85
|
+
|
|
86
|
+
def get_snapshot_dataframe(
|
|
87
|
+
self, dataset_id: int, labelset_id: int, file_info: bool = True, **kwargs
|
|
88
|
+
) -> "pd.DataFrame":
|
|
89
|
+
"""Download a snapshot. For additional arguments,
|
|
90
|
+
see documentation for CreateExport in the Python SDK.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
dataset_id (int): dataset ID you're interested in
|
|
94
|
+
labelset_id (int): ID of your labelset (from teach task)
|
|
95
|
+
file_info (bool, optional): Include additional file level metadata.
|
|
96
|
+
Defaults to True.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
pd.DataFrame: DataFrame with full document text and additional metadata
|
|
100
|
+
"""
|
|
101
|
+
export = self._create_export(
|
|
102
|
+
dataset_id, labelset_id, file_info=file_info, **kwargs
|
|
103
|
+
)
|
|
104
|
+
return self._download_export(export.id)
|
|
105
|
+
|
|
106
|
+
def _download_pdfs_from_export(
|
|
107
|
+
self,
|
|
108
|
+
export_df: "pd.DataFrame",
|
|
109
|
+
output_dir: str,
|
|
110
|
+
file_name_col: str,
|
|
111
|
+
file_url_col: str,
|
|
112
|
+
max_files_to_download: int = None,
|
|
113
|
+
) -> int:
|
|
114
|
+
try:
|
|
115
|
+
import tqdm
|
|
116
|
+
except ImportError as error:
|
|
117
|
+
raise RuntimeError(
|
|
118
|
+
"downloading pdfs requires additional dependencies: "
|
|
119
|
+
"`pip install indico-toolkit[downloads]`"
|
|
120
|
+
) from error
|
|
121
|
+
|
|
122
|
+
for i, row in tqdm.tqdm(export_df.iterrows()):
|
|
123
|
+
basename = os.path.basename(row[file_name_col])
|
|
124
|
+
pdf_bytes = self._retrieve_storage_object(row[file_url_col])
|
|
125
|
+
with open(os.path.join(output_dir, basename), "wb") as fd:
|
|
126
|
+
fd.write(pdf_bytes)
|
|
127
|
+
if max_files_to_download and i + 1 == max_files_to_download:
|
|
128
|
+
return max_files_to_download
|
|
129
|
+
return export_df.shape[0]
|
|
130
|
+
|
|
131
|
+
@retry(IndicoRequestError, ConnectionError)
|
|
132
|
+
def _download_export(self, export_id: int) -> "pd.DataFrame":
|
|
133
|
+
"""
|
|
134
|
+
Download a dataframe representation of your dataset export
|
|
135
|
+
"""
|
|
136
|
+
return self.client.call(DownloadExport(export_id=export_id))
|
|
137
|
+
|
|
138
|
+
@retry(IndicoRequestError, ConnectionError)
|
|
139
|
+
def _create_export(
|
|
140
|
+
self,
|
|
141
|
+
dataset_id: int,
|
|
142
|
+
labelset_id: int,
|
|
143
|
+
file_info: bool = True,
|
|
144
|
+
wait: bool = True,
|
|
145
|
+
**kwargs,
|
|
146
|
+
) -> Export:
|
|
147
|
+
"""
|
|
148
|
+
Create an export object that can be used to download datasets. For additional
|
|
149
|
+
kwargs, see CreateExport docstring in the Python SDK.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
dataset_id (int): ID of your dataset
|
|
153
|
+
labelset_id (int): ID of your labelset (from teach task)
|
|
154
|
+
file_info (bool, optional): whether to include additional file metadata.
|
|
155
|
+
Defaults to True.
|
|
156
|
+
wait (bool, optional): wait for export to be created. Defaults to True.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Export: Description of dataset assets. See Python SDK for full object
|
|
160
|
+
description
|
|
161
|
+
"""
|
|
162
|
+
return self.client.call(
|
|
163
|
+
CreateExport(
|
|
164
|
+
dataset_id=dataset_id,
|
|
165
|
+
file_info=file_info,
|
|
166
|
+
labelset_id=labelset_id,
|
|
167
|
+
wait=wait,
|
|
168
|
+
**kwargs,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
@retry(IndicoRequestError, ConnectionError)
|
|
173
|
+
def _retrieve_storage_object(self, url: str):
|
|
174
|
+
return self.client.call(RetrieveStorageObject(url))
|
|
175
|
+
|
|
176
|
+
def _get_csv_download_url(self, dataset_id: int) -> str:
|
|
177
|
+
query = """
|
|
178
|
+
query getDataURL($id: Int!) {
|
|
179
|
+
dataset(id: $id) {
|
|
180
|
+
files {
|
|
181
|
+
fileType
|
|
182
|
+
id
|
|
183
|
+
rainbowUrl
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
"""
|
|
188
|
+
result = self.client.call(GraphQLRequest(query, {"id": dataset_id}))
|
|
189
|
+
# loop through in case there are other file types uploaded to dataset
|
|
190
|
+
for file in result["dataset"]["files"]:
|
|
191
|
+
if file["fileType"] == "CSV":
|
|
192
|
+
return file["rainbowUrl"]
|
|
193
|
+
raise ToolkitInputError(f"There are no CSVs uploaded to {dataset_id}")
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from typing import List, Union
|
|
2
|
+
|
|
3
|
+
from indico import IndicoClient
|
|
4
|
+
from indico.errors import IndicoRequestError
|
|
5
|
+
from indico.queries import (
|
|
6
|
+
AddModelGroupComponent,
|
|
7
|
+
CreateStorageURLs,
|
|
8
|
+
GraphQLRequest,
|
|
9
|
+
JobStatus,
|
|
10
|
+
ModelGroupPredict,
|
|
11
|
+
RetrieveStorageObject,
|
|
12
|
+
)
|
|
13
|
+
from indico.types import Dataset, ModelGroup, Workflow
|
|
14
|
+
|
|
15
|
+
from ..errors import ToolkitStatusError
|
|
16
|
+
from ..retry import retry
|
|
17
|
+
from ..types import Predictions
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class IndicoWrapper:
|
|
21
|
+
"""
|
|
22
|
+
Class for shared API functionality
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, client: IndicoClient):
|
|
26
|
+
"""
|
|
27
|
+
Create indico client with user provided arguments
|
|
28
|
+
|
|
29
|
+
args:
|
|
30
|
+
client (IndicoClient): instantiated Indico Client object
|
|
31
|
+
"""
|
|
32
|
+
self.client = client
|
|
33
|
+
|
|
34
|
+
def train_model(
|
|
35
|
+
self,
|
|
36
|
+
dataset: Dataset,
|
|
37
|
+
workflow: Workflow,
|
|
38
|
+
model_name: str,
|
|
39
|
+
source_col: str,
|
|
40
|
+
target_col: str,
|
|
41
|
+
after_component_id: int = None,
|
|
42
|
+
) -> ModelGroup:
|
|
43
|
+
"""
|
|
44
|
+
Train an Indico model
|
|
45
|
+
Args:
|
|
46
|
+
dataset (Dataset): A dataset object (should represent an uploaded CSV
|
|
47
|
+
dataset)
|
|
48
|
+
workflow (Workflow): A workflow object to the corresponding dataset
|
|
49
|
+
model_name (str): the name for your model
|
|
50
|
+
source_col (str): the csv column that contained the text
|
|
51
|
+
target_col (str): the csv column that contained the labels
|
|
52
|
+
after_component_id (int, optional): The workflow component that precedes
|
|
53
|
+
this model group. If None, will be set programmatically to the id of the
|
|
54
|
+
Input OCR Extraction component. Defaults to None.
|
|
55
|
+
wait (bool, optional): Wait for the model to finish training.
|
|
56
|
+
Defaults to False.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
ModelGroup: Model group object
|
|
60
|
+
"""
|
|
61
|
+
if not after_component_id:
|
|
62
|
+
after_component_id = workflow.component_by_type("INPUT_OCR_EXTRACTION").id
|
|
63
|
+
return self.client.call(
|
|
64
|
+
AddModelGroupComponent(
|
|
65
|
+
name=model_name,
|
|
66
|
+
dataset_id=dataset.id,
|
|
67
|
+
source_column_id=dataset.datacolumn_by_name(source_col).id,
|
|
68
|
+
labelset_column_id=dataset.labelset_by_name(target_col).id,
|
|
69
|
+
workflow_id=workflow.id,
|
|
70
|
+
after_component_id=after_component_id,
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@retry(IndicoRequestError, ConnectionError)
|
|
75
|
+
def get_storage_object(self, storage_url: str):
|
|
76
|
+
return self.client.call(RetrieveStorageObject(storage_url))
|
|
77
|
+
|
|
78
|
+
def create_storage_urls(self, file_paths: List[str]) -> List[str]:
|
|
79
|
+
return self.client.call(CreateStorageURLs(files=file_paths))
|
|
80
|
+
|
|
81
|
+
def get_job_status(self, job_id: int, wait: bool = True, timeout: float = None):
|
|
82
|
+
return self.client.call(JobStatus(id=job_id, wait=wait, timeout=timeout))
|
|
83
|
+
|
|
84
|
+
@retry(IndicoRequestError, ConnectionError)
|
|
85
|
+
def graphQL_request(self, graphql_query: str, variables: dict = None):
|
|
86
|
+
return self.client.call(
|
|
87
|
+
GraphQLRequest(query=graphql_query, variables=variables)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def get_predictions_with_model_id(
|
|
91
|
+
self,
|
|
92
|
+
model_id: int,
|
|
93
|
+
samples: List[str],
|
|
94
|
+
load: bool = False,
|
|
95
|
+
options: dict = None,
|
|
96
|
+
wait: bool = True,
|
|
97
|
+
) -> Union[int, List[Predictions]]:
|
|
98
|
+
"""
|
|
99
|
+
Submit samples directly to a model. Note: documents must already be in raw text.
|
|
100
|
+
Args:
|
|
101
|
+
model_id (int): The model ID to submit to
|
|
102
|
+
samples (List[str]): A list containing the text samples you want to submit
|
|
103
|
+
load (bool, optional): [deprecated] Always set to False. Defaults to False.
|
|
104
|
+
options (dict, optional): Model Prediction options. Defaults to None.
|
|
105
|
+
wait (bool, optional): Wait for predictions to finish. Defaults to True.
|
|
106
|
+
|
|
107
|
+
Returns: if wait is False, returns the job ID, else returns a list of
|
|
108
|
+
Predictions where each Predictions is either type Classifications or
|
|
109
|
+
Extractions depending on your model.
|
|
110
|
+
"""
|
|
111
|
+
job = self.client.call(ModelGroupPredict(model_id, samples, load, options))
|
|
112
|
+
if not wait:
|
|
113
|
+
return job.id
|
|
114
|
+
status = self.get_job_status(job.id, wait=True)
|
|
115
|
+
if status.status != "SUCCESS":
|
|
116
|
+
raise ToolkitStatusError(
|
|
117
|
+
f"Predictions Failed, {status.status}: {status.result}"
|
|
118
|
+
)
|
|
119
|
+
return [Predictions.get_obj(i) for i in status.result]
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Iterator
|
|
3
|
+
|
|
4
|
+
from indico import IndicoClient
|
|
5
|
+
from indico.queries import GraphQLRequest
|
|
6
|
+
|
|
7
|
+
# valid model option parameters
|
|
8
|
+
TEXT_EXTRACTION_PARAMS = {
|
|
9
|
+
"max_empty_chunk_ratio": lambda value: 0 <= value <= 1.0e5,
|
|
10
|
+
"auto_negative_scaling": [True, False],
|
|
11
|
+
"optimize_for": [
|
|
12
|
+
"predict_speed",
|
|
13
|
+
"accuracy",
|
|
14
|
+
"speed",
|
|
15
|
+
"accuracy_fp16",
|
|
16
|
+
"predict_speed_fp16",
|
|
17
|
+
],
|
|
18
|
+
"subtoken_predictions": [True, False],
|
|
19
|
+
"base_model": [
|
|
20
|
+
"roberta",
|
|
21
|
+
"small",
|
|
22
|
+
"multilingual",
|
|
23
|
+
"fast",
|
|
24
|
+
"textcnn",
|
|
25
|
+
"fasttextcnn",
|
|
26
|
+
],
|
|
27
|
+
"class_weight": ["linear", "sqrt", "log", None],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
TEXT_CLASSIFICATION_PARAMS = {
|
|
31
|
+
"model_type": ["tfidf_lr", "tfidf_gbt", "standard", "finetune"]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ModelOp:
|
|
36
|
+
"""
|
|
37
|
+
Class to assit in Model Group Operation calls.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, client: IndicoClient):
|
|
41
|
+
self.client = client
|
|
42
|
+
|
|
43
|
+
def get_model_options(
|
|
44
|
+
self, model_group_id: int, model_id: int | None = None
|
|
45
|
+
) -> dict[str, object]:
|
|
46
|
+
"""
|
|
47
|
+
Return model options for `model_id` of `model_group_id`
|
|
48
|
+
or the most recent model if `model_id` is not specified.
|
|
49
|
+
Args:
|
|
50
|
+
model_group_id (int): id of model group
|
|
51
|
+
model_id (int, optional): argument to return a specific model within a model
|
|
52
|
+
group
|
|
53
|
+
"""
|
|
54
|
+
all_model_options = self.get_all_model_options(model_group_id)
|
|
55
|
+
|
|
56
|
+
if model_id is None:
|
|
57
|
+
return next(all_model_options)
|
|
58
|
+
|
|
59
|
+
for model_options in all_model_options:
|
|
60
|
+
if model_options["id"] == model_id:
|
|
61
|
+
return model_options
|
|
62
|
+
else:
|
|
63
|
+
raise RuntimeError(
|
|
64
|
+
f"Model group {model_group_id} does not have a model with ID {model_id}"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def get_all_model_options(self, model_group_id: int) -> Iterator[dict[str, object]]:
|
|
68
|
+
"""
|
|
69
|
+
Return model options for all models of `model_group_id`.
|
|
70
|
+
Args:
|
|
71
|
+
model_group_id (int): id of model group
|
|
72
|
+
Returns:
|
|
73
|
+
dict: dictionary of all model options, including:
|
|
74
|
+
"id", "domain, ""high_quality", "interlabeler_resolution",
|
|
75
|
+
"sampling_strategy", "seed", "test_split", "weight_by_class_frequency",
|
|
76
|
+
"word_predictor_strength", and "model_training_options"
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
response = self.client.call(
|
|
80
|
+
GraphQLRequest(
|
|
81
|
+
"""
|
|
82
|
+
query getMoonbowModelGroupModels($modelGroupId: Int!) {
|
|
83
|
+
modelGroup(modelGroupId: $modelGroupId) {
|
|
84
|
+
id
|
|
85
|
+
models {
|
|
86
|
+
id
|
|
87
|
+
modelOptions
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
""",
|
|
92
|
+
{"modelGroupId": model_group_id},
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
for model in response["modelGroup"]["models"]:
|
|
97
|
+
model_options = json.loads(model["modelOptions"])
|
|
98
|
+
model_options["id"] = model["id"]
|
|
99
|
+
yield model_options
|
|
100
|
+
|
|
101
|
+
def retrain_model(self, model_group_id: int) -> dict:
|
|
102
|
+
"""
|
|
103
|
+
Forces model retrain.
|
|
104
|
+
Args:
|
|
105
|
+
model_group_id (int): id of model group needing retraining
|
|
106
|
+
Returns:
|
|
107
|
+
dict: returns model group id and status of retraining model
|
|
108
|
+
"""
|
|
109
|
+
return self.client.call(
|
|
110
|
+
GraphQLRequest(
|
|
111
|
+
"""
|
|
112
|
+
mutation retrainMoonbowModelGroup($modelGroupId: Int!) {
|
|
113
|
+
retrainModelGroup(modelGroupId: $modelGroupId, forceRetrain: true) {
|
|
114
|
+
id
|
|
115
|
+
status
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
""",
|
|
119
|
+
{"modelGroupId": model_group_id},
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def update_model_settings(
|
|
124
|
+
self, model_group_id: int, model_type: str, **model_training_options
|
|
125
|
+
) -> dict[str, object]:
|
|
126
|
+
"""
|
|
127
|
+
Update group model settings based on specified model training options.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
model_group_id (int): id of model group
|
|
131
|
+
model_type (str): type of model ("text_extraction", "text_classification")
|
|
132
|
+
**kwargs: Advanced Model Training Options
|
|
133
|
+
|
|
134
|
+
Default values of Advanced Model Training Options and avaiable parameters:
|
|
135
|
+
For Text Extraction Model:
|
|
136
|
+
max_empty_chunk_ratio : 1.0 (min of 0, no max value: a large
|
|
137
|
+
number effectivly turns this option off)
|
|
138
|
+
auto_negative_scaling : True
|
|
139
|
+
optimize_for : "predict_speed" ( "predict_speed", "accuracy",
|
|
140
|
+
"speed", "accuracy_fp16" and "predict_speed_fp16")
|
|
141
|
+
subtoken_predictions : True
|
|
142
|
+
base_model : "roberta" ("roberta", "small" (distilled version of
|
|
143
|
+
RoBERTa), "multilingual", "fast", "textcnn", "fasttextcnn")
|
|
144
|
+
class_weight : "sqrt" ("linear", "sqrt", "log", None)
|
|
145
|
+
|
|
146
|
+
For Text Classification Model:
|
|
147
|
+
model_type : "standard" ("tfidf_lr", "tfidf_gbt", "standard",
|
|
148
|
+
"finetune")
|
|
149
|
+
Returns:
|
|
150
|
+
dict: Dictionary of advanced model training options
|
|
151
|
+
"""
|
|
152
|
+
model = self.client.call(
|
|
153
|
+
GraphQLRequest(
|
|
154
|
+
"""
|
|
155
|
+
mutation updateModelGroup(
|
|
156
|
+
$modelGroupId: Int!,
|
|
157
|
+
$modelTrainingOptions: JSONString
|
|
158
|
+
) {
|
|
159
|
+
updateModelGroupSettings(
|
|
160
|
+
modelGroupId: $modelGroupId
|
|
161
|
+
modelTrainingOptions: $modelTrainingOptions
|
|
162
|
+
) {
|
|
163
|
+
modelOptions {
|
|
164
|
+
id
|
|
165
|
+
modelTrainingOptions
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
""",
|
|
170
|
+
{
|
|
171
|
+
"modelGroupId": model_group_id,
|
|
172
|
+
"modelTrainingOptions": json.dumps(
|
|
173
|
+
self._parameter_check(model_type, **model_training_options)
|
|
174
|
+
),
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
options = json.loads(
|
|
179
|
+
model["updateModelGroupSettings"]["modelOptions"]["modelTrainingOptions"]
|
|
180
|
+
)
|
|
181
|
+
options["id"] = model["updateModelGroupSettings"]["modelOptions"]["id"]
|
|
182
|
+
return options
|
|
183
|
+
|
|
184
|
+
def _parameter_check(self, model_type, **model_training_options):
|
|
185
|
+
if model_type == "text_extraction":
|
|
186
|
+
valid_params = TEXT_EXTRACTION_PARAMS
|
|
187
|
+
elif model_type == "text_classification":
|
|
188
|
+
valid_params = TEXT_CLASSIFICATION_PARAMS
|
|
189
|
+
else:
|
|
190
|
+
raise ValueError(f"Invalid model type: {model_type}")
|
|
191
|
+
|
|
192
|
+
for param, value in model_training_options.items():
|
|
193
|
+
if param not in valid_params:
|
|
194
|
+
raise ValueError(f"Invalid parameter for {model_type}: {param}")
|
|
195
|
+
if callable(valid_params[param]) and not valid_params[param](value):
|
|
196
|
+
raise ValueError(f"Invalid value for parameter '{param}': {value}")
|
|
197
|
+
if value not in valid_params[param]:
|
|
198
|
+
raise ValueError(f"Invalid value for parameter '{param}': {value}")
|
|
199
|
+
|
|
200
|
+
return model_training_options
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from indico import IndicoClient
|
|
4
|
+
|
|
5
|
+
from .indico_wrapper import Workflow
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Reviewer(Workflow):
|
|
9
|
+
"""
|
|
10
|
+
Class to simulate human reviewer
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
client: IndicoClient,
|
|
16
|
+
workflow_id: int,
|
|
17
|
+
):
|
|
18
|
+
self.client = client
|
|
19
|
+
self.workflow_id = workflow_id
|
|
20
|
+
|
|
21
|
+
def accept_review(self, submission_id: int, changes: dict) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Accept a submission in the review queue
|
|
24
|
+
Args:
|
|
25
|
+
submission_id (int): submission ID
|
|
26
|
+
changes (dict): accepted predictions with format like,
|
|
27
|
+
e.g. {"model_name": [{"label"...}]}
|
|
28
|
+
"""
|
|
29
|
+
self.graphQL_request(
|
|
30
|
+
SUBMIT_REVIEW,
|
|
31
|
+
{
|
|
32
|
+
"rejected": False,
|
|
33
|
+
"submissionId": submission_id,
|
|
34
|
+
"changes": json.dumps(changes),
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def get_random_review_id(self):
|
|
39
|
+
response = self.graphQL_request(
|
|
40
|
+
GET_RANDOM_REVIEW_ID, {"workflowId": self.workflow_id}
|
|
41
|
+
)
|
|
42
|
+
try:
|
|
43
|
+
return response["randomSubmission"]["id"]
|
|
44
|
+
except Exception:
|
|
45
|
+
raise RuntimeError("The review queue is empty")
|
|
46
|
+
|
|
47
|
+
def get_random_exception_id(self):
|
|
48
|
+
response = self.graphQL_request(
|
|
49
|
+
GET_RANDOM_EXCEPTION_ID, {"workflowId": self.workflow_id}
|
|
50
|
+
)
|
|
51
|
+
try:
|
|
52
|
+
return response["randomSubmission"]["id"]
|
|
53
|
+
except Exception:
|
|
54
|
+
raise RuntimeError("The exception queue is empty")
|
|
55
|
+
|
|
56
|
+
def reject_submission(self, submission_id):
|
|
57
|
+
return self.graphQL_request(
|
|
58
|
+
SUBMIT_REVIEW, {"rejected": True, "submissionId": submission_id}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
SUBMIT_REVIEW = """
|
|
63
|
+
mutation submitStandardQueue(
|
|
64
|
+
$changes: JSONString,
|
|
65
|
+
$rejected: Boolean,
|
|
66
|
+
$submissionId: Int!,
|
|
67
|
+
$notes: String
|
|
68
|
+
) {
|
|
69
|
+
submitReview(
|
|
70
|
+
changes: $changes,
|
|
71
|
+
rejected: $rejected,
|
|
72
|
+
submissionId: $submissionId,
|
|
73
|
+
notes: $notes
|
|
74
|
+
) {
|
|
75
|
+
id
|
|
76
|
+
__typename
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
GET_RANDOM_EXCEPTION_ID = """
|
|
82
|
+
query getExceptionsSubmission($workflowId: Int!) {
|
|
83
|
+
randomSubmission(adminReview: true, workflowId: $workflowId) {
|
|
84
|
+
id
|
|
85
|
+
resultFile
|
|
86
|
+
inputFilename
|
|
87
|
+
autoReview {
|
|
88
|
+
id
|
|
89
|
+
changes
|
|
90
|
+
__typename
|
|
91
|
+
}
|
|
92
|
+
__typename
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
GET_RANDOM_REVIEW_ID = """
|
|
98
|
+
query getSubmission($workflowId: Int!) {
|
|
99
|
+
randomSubmission(adminReview: false, workflowId: $workflowId) {
|
|
100
|
+
id
|
|
101
|
+
resultFile
|
|
102
|
+
inputFilename
|
|
103
|
+
autoReview {
|
|
104
|
+
id
|
|
105
|
+
changes
|
|
106
|
+
__typename
|
|
107
|
+
}
|
|
108
|
+
__typename
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
"""
|