lewtun HF staff commited on
Commit
7a3b085
β€’
2 Parent(s): a22b4e9 3207694

Merge pull request #5 from huggingface/use-dataset-backend

Browse files
Files changed (4) hide show
  1. .gitignore +2 -0
  2. app.py +204 -65
  3. requirements.txt +1 -1
  4. utils.py +38 -10
.gitignore CHANGED
@@ -127,3 +127,5 @@ dmypy.json
127
 
128
  # Pyre type checker
129
  .pyre/
 
 
 
127
 
128
  # Pyre type checker
129
  .pyre/
130
+
131
+ scratch/
app.py CHANGED
@@ -1,10 +1,14 @@
1
  import os
 
2
  from pathlib import Path
3
 
 
4
  import streamlit as st
 
5
  from dotenv import load_dotenv
 
6
 
7
- from utils import get_compatible_models, get_metadata, http_post
8
 
9
  if Path(".env").is_file():
10
  load_dotenv(".env")
@@ -12,22 +16,19 @@ if Path(".env").is_file():
12
  HF_TOKEN = os.getenv("HF_TOKEN")
13
  AUTOTRAIN_USERNAME = os.getenv("AUTOTRAIN_USERNAME")
14
  AUTOTRAIN_BACKEND_API = os.getenv("AUTOTRAIN_BACKEND_API")
 
15
 
16
 
17
  TASK_TO_ID = {
18
  "binary_classification": 1,
19
  "multi_class_classification": 2,
20
- "multi_label_classification": 3,
21
  "entity_extraction": 4,
22
  "extractive_question_answering": 5,
23
  "translation": 6,
24
  "summarization": 8,
25
- "single_column_regression": 10,
26
  }
27
 
28
- # TODO: remove this hardcorded logic and accept any dataset on the Hub
29
- DATASETS_TO_EVALUATE = ["emotion", "conll2003", "imdb", "squad", "xsum", "ncbi_disease", "go_emotions"]
30
-
31
  ###########
32
  ### APP ###
33
  ###########
@@ -42,90 +43,228 @@ st.markdown(
42
  """
43
  )
44
 
45
- selectable_datasets = [f"lewtun/autoevaluate__{dset}" for dset in DATASETS_TO_EVALUATE]
46
-
47
  query_params = st.experimental_get_query_params()
48
- default_dataset = selectable_datasets[0]
49
  if "dataset" in query_params:
50
- if len(query_params["dataset"]) > 0 and query_params["dataset"][0] in selectable_datasets:
51
  default_dataset = query_params["dataset"][0]
52
 
53
- dataset_name = st.selectbox(
54
- "Select a dataset",
55
- selectable_datasets,
56
- index=selectable_datasets.index(default_dataset)
57
- )
58
-
59
- st.experimental_set_query_params(**{"dataset": [dataset]})
60
 
61
- # TODO: remove this step once we select real datasets
62
- # Strip out original dataset name
63
- original_dataset_name = dataset_name.split("/")[-1].split("__")[-1]
64
 
65
- # In general this will be a list of multiple configs => need to generalise logic here
66
- metadata = get_metadata(dataset_name)
 
 
67
 
68
  with st.expander("Advanced configuration"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- dataset_config = st.selectbox("Select a config", [metadata[0]["config"]])
71
 
72
- splits = metadata[0]["splits"]
73
- split_names = list(splits.values())
74
- eval_split = splits.get("eval_split", split_names[0])
 
 
 
 
 
 
 
75
 
76
- selected_split = st.selectbox("Select a split", split_names, index=split_names.index(eval_split))
77
 
78
  # TODO: add a function to handle the mapping task <--> column mapping
79
- col_mapping = metadata[0]["col_mapping"]
80
- col_names = list(col_mapping.keys())
81
 
82
- # TODO: figure out how to get all dataset column names (i.e. features) without download dataset itself
83
  st.markdown("**Map your data columns**")
84
  col1, col2 = st.columns(2)
85
 
86
  # TODO: find a better way to layout these items
87
- # TODO: propagate this information to payload
88
- with col1:
89
- st.markdown("`text` column")
90
- st.text("")
91
- st.text("")
92
- st.text("")
93
- st.text("")
94
- st.markdown("`target` column")
95
- with col2:
96
- st.selectbox("This column should contain the text you want to classify", col_names, index=0)
97
- st.selectbox("This column should contain the labels you want to assign to the text", col_names, index=1)
 
 
 
 
 
98
 
99
- with st.form(key="form"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- compatible_models = get_compatible_models(metadata[0]["task"], original_dataset_name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
- selected_models = st.multiselect("Select the models you wish to evaluate", compatible_models, compatible_models[0])
 
 
 
 
 
 
 
 
 
 
 
 
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  submit_button = st.form_submit_button("Make submission")
106
 
107
  if submit_button:
108
- for model in selected_models:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  payload = {
110
- "username": AUTOTRAIN_USERNAME,
111
- "task": TASK_TO_ID[metadata[0]["task_id"]],
112
- "model": model,
113
- "col_mapping": metadata[0]["col_mapping"],
114
- "split": selected_split,
115
- "dataset": original_dataset_name,
116
- "config": dataset_config,
117
  }
118
- json_resp = http_post(
119
- path="/evaluate/create", payload=payload, token=HF_TOKEN, domain=AUTOTRAIN_BACKEND_API
 
 
 
 
120
  ).json()
121
- if json_resp["status"] == 1:
122
- st.success(f"βœ… Successfully submitted model {model} for evaluation with job ID {json_resp['id']}")
123
- st.markdown(
124
- f"""
125
- Evaluation takes appoximately 1 hour to complete, so grab a β˜• or 🍡 while you wait:
126
-
127
- * πŸ“Š Click [here](https://huggingface.co/spaces/autoevaluate/leaderboards) to view the results from your submission
128
- """
129
- )
130
- else:
131
- st.error("πŸ™ˆ Oh noes, there was an error submitting your submission!")
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import uuid
3
  from pathlib import Path
4
 
5
+ import pandas as pd
6
  import streamlit as st
7
+ from datasets import get_dataset_config_names
8
  from dotenv import load_dotenv
9
+ from huggingface_hub import list_datasets
10
 
11
+ from utils import get_compatible_models, get_metadata, http_get, http_post
12
 
13
  if Path(".env").is_file():
14
  load_dotenv(".env")
 
16
  HF_TOKEN = os.getenv("HF_TOKEN")
17
  AUTOTRAIN_USERNAME = os.getenv("AUTOTRAIN_USERNAME")
18
  AUTOTRAIN_BACKEND_API = os.getenv("AUTOTRAIN_BACKEND_API")
19
+ DATASETS_PREVIEW_API = os.getenv("DATASETS_PREVIEW_API")
20
 
21
 
22
  TASK_TO_ID = {
23
  "binary_classification": 1,
24
  "multi_class_classification": 2,
25
+ # "multi_label_classification": 3, # Not fully supported in AutoTrain
26
  "entity_extraction": 4,
27
  "extractive_question_answering": 5,
28
  "translation": 6,
29
  "summarization": 8,
 
30
  }
31
 
 
 
 
32
  ###########
33
  ### APP ###
34
  ###########
 
43
  """
44
  )
45
 
46
+ all_datasets = [d.id for d in list_datasets()]
 
47
  query_params = st.experimental_get_query_params()
48
+ default_dataset = all_datasets[0]
49
  if "dataset" in query_params:
50
+ if len(query_params["dataset"]) > 0 and query_params["dataset"][0] in all_datasets:
51
  default_dataset = query_params["dataset"][0]
52
 
53
+ selected_dataset = st.selectbox("Select a dataset", all_datasets, index=all_datasets.index(default_dataset))
54
+ st.experimental_set_query_params(**{"dataset": [selected_dataset]})
 
 
 
 
 
55
 
 
 
 
56
 
57
+ # TODO: In general this will be a list of multiple configs => need to generalise logic here
58
+ metadata = get_metadata(selected_dataset)
59
+ if metadata is None:
60
+ st.warning("No evaluation metadata found. Please configure the evaluation job below.")
61
 
62
  with st.expander("Advanced configuration"):
63
+ ## Select task
64
+ selected_task = st.selectbox("Select a task", list(TASK_TO_ID.keys()))
65
+ ### Select config
66
+ configs = get_dataset_config_names(selected_dataset)
67
+ selected_config = st.selectbox("Select a config", configs)
68
+
69
+ ## Select splits
70
+ splits_resp = http_get(path="/splits", domain=DATASETS_PREVIEW_API, params={"dataset": selected_dataset})
71
+ if splits_resp.status_code == 200:
72
+ split_names = []
73
+ all_splits = splits_resp.json()
74
+ for split in all_splits["splits"]:
75
+ if split["config"] == selected_config:
76
+ split_names.append(split["split"])
77
 
78
+ selected_split = st.selectbox("Select a split", split_names) # , index=split_names.index(eval_split))
79
 
80
+ ## Show columns
81
+ rows_resp = http_get(
82
+ path="/rows",
83
+ domain="https://datasets-preview.huggingface.tech",
84
+ params={"dataset": selected_dataset, "config": selected_config, "split": selected_split},
85
+ ).json()
86
+ col_names = list(pd.json_normalize(rows_resp["rows"][0]["row"]).columns)
87
+ # splits = metadata[0]["splits"]
88
+ # split_names = list(splits.values())
89
+ # eval_split = splits.get("eval_split", split_names[0])
90
 
91
+ # selected_split = st.selectbox("Select a split", split_names, index=split_names.index(eval_split))
92
 
93
  # TODO: add a function to handle the mapping task <--> column mapping
94
+ # col_mapping = metadata[0]["col_mapping"]
95
+ # col_names = list(col_mapping.keys())
96
 
 
97
  st.markdown("**Map your data columns**")
98
  col1, col2 = st.columns(2)
99
 
100
  # TODO: find a better way to layout these items
101
+ col_mapping = {}
102
+ if selected_task in ["binary_classification", "multi_class_classification"]:
103
+ with col1:
104
+ st.markdown("`text` column")
105
+ st.text("")
106
+ st.text("")
107
+ st.text("")
108
+ st.text("")
109
+ st.markdown("`target` column")
110
+ with col2:
111
+ text_col = st.selectbox("This column should contain the text you want to classify", col_names)
112
+ target_col = st.selectbox(
113
+ "This column should contain the labels you want to assign to the text", col_names
114
+ )
115
+ col_mapping[text_col] = "text"
116
+ col_mapping[target_col] = "target"
117
 
118
+ elif selected_task == "entity_extraction":
119
+ with col1:
120
+ st.markdown("`tokens` column")
121
+ st.text("")
122
+ st.text("")
123
+ st.text("")
124
+ st.text("")
125
+ st.markdown("`tags` column")
126
+ with col2:
127
+ tokens_col = st.selectbox(
128
+ "This column should contain the parts of the text (as an array of tokens) you want to assign labels to",
129
+ col_names,
130
+ )
131
+ tags_col = st.selectbox(
132
+ "This column should contain the labels to associate to each part of the text", col_names
133
+ )
134
+ col_mapping[tokens_col] = "tokens"
135
+ col_mapping[tags_col] = "tags"
136
 
137
+ elif selected_task == "translation":
138
+ with col1:
139
+ st.markdown("`source` column")
140
+ st.text("")
141
+ st.text("")
142
+ st.text("")
143
+ st.text("")
144
+ st.markdown("`target` column")
145
+ with col2:
146
+ text_col = st.selectbox("This column should contain the text you want to translate", col_names)
147
+ target_col = st.selectbox(
148
+ "This column should contain an example translation of the source text", col_names
149
+ )
150
+ col_mapping[text_col] = "source"
151
+ col_mapping[target_col] = "target"
152
 
153
+ elif selected_task == "summarization":
154
+ with col1:
155
+ st.markdown("`text` column")
156
+ st.text("")
157
+ st.text("")
158
+ st.text("")
159
+ st.text("")
160
+ st.markdown("`target` column")
161
+ with col2:
162
+ text_col = st.selectbox("This column should contain the text you want to summarize", col_names)
163
+ target_col = st.selectbox("This column should contain an example summarization of the text", col_names)
164
+ col_mapping[text_col] = "text"
165
+ col_mapping[target_col] = "target"
166
 
167
+ elif selected_task == "extractive_question_answering":
168
+ with col1:
169
+ st.markdown("`context` column")
170
+ st.text("")
171
+ st.text("")
172
+ st.text("")
173
+ st.text("")
174
+ st.markdown("`question` column")
175
+ st.text("")
176
+ st.text("")
177
+ st.text("")
178
+ st.text("")
179
+ st.markdown("`answers.text` column")
180
+ st.text("")
181
+ st.text("")
182
+ st.text("")
183
+ st.text("")
184
+ st.markdown("`answers.answer_start` column")
185
+ with col2:
186
+ context_col = st.selectbox("This column should contain the question's context", col_names)
187
+ question_col = st.selectbox(
188
+ "This column should contain the question to be answered, given the context", col_names
189
+ )
190
+ answers_text_col = st.selectbox(
191
+ "This column should contain example answers to the question, extracted from the context", col_names
192
+ )
193
+ answers_start_col = st.selectbox(
194
+ "This column should contain the indices in the context of the first character of each answers.text",
195
+ col_names,
196
+ )
197
+ col_mapping[context_col] = "context"
198
+ col_mapping[question_col] = "question"
199
+ col_mapping[answers_text_col] = "answers.text"
200
+ col_mapping[answers_start_col] = "answers.answer_start"
201
+
202
+ with st.form(key="form"):
203
+
204
+ compatible_models = get_compatible_models(selected_task, selected_dataset)
205
+
206
+ selected_models = st.multiselect(
207
+ "Select the models you wish to evaluate", compatible_models
208
+ )
209
  submit_button = st.form_submit_button("Make submission")
210
 
211
  if submit_button:
212
+ project_id = str(uuid.uuid4())[:3]
213
+ payload = {
214
+ "username": AUTOTRAIN_USERNAME,
215
+ "proj_name": f"my-eval-project-{project_id}",
216
+ "task": TASK_TO_ID[selected_task],
217
+ "config": {
218
+ "language": "en",
219
+ "max_models": 5,
220
+ "instance": {
221
+ "provider": "aws",
222
+ "instance_type": "ml.g4dn.4xlarge",
223
+ "max_runtime_seconds": 172800,
224
+ "num_instances": 1,
225
+ "disk_size_gb": 150,
226
+ },
227
+ "evaluation": {
228
+ "metrics": [],
229
+ "models": selected_models,
230
+ },
231
+ },
232
+ }
233
+ print(f"Payload: {payload}")
234
+ project_json_resp = http_post(
235
+ path="/projects/create", payload=payload, token=HF_TOKEN, domain=AUTOTRAIN_BACKEND_API
236
+ ).json()
237
+ print(project_json_resp)
238
+
239
+ if project_json_resp["created"]:
240
  payload = {
241
+ "split": 4, # use "auto" split choice in AutoTrain
242
+ "col_mapping": col_mapping,
243
+ "load_config": {"max_size_bytes": 0, "shuffle": False},
 
 
 
 
244
  }
245
+ data_json_resp = http_post(
246
+ path=f"/projects/{project_json_resp['id']}/data/{selected_dataset}",
247
+ payload=payload,
248
+ token=HF_TOKEN,
249
+ domain=AUTOTRAIN_BACKEND_API,
250
+ params={"type": "dataset", "config_name": selected_config, "split_name": selected_split},
251
  ).json()
252
+ print(data_json_resp)
253
+ if data_json_resp["download_status"] == 1:
254
+ train_json_resp = http_get(
255
+ path=f"/projects/{project_json_resp['id']}/data/start_process",
256
+ token=HF_TOKEN,
257
+ domain=AUTOTRAIN_BACKEND_API,
258
+ ).json()
259
+ print(train_json_resp)
260
+ if train_json_resp["success"]:
261
+ st.success(f"βœ… Successfully submitted evaluation job with project ID {project_id}")
262
+ st.markdown(
263
+ f"""
264
+ Evaluation takes appoximately 1 hour to complete, so grab a β˜• or 🍡 while you wait:
265
+
266
+ * πŸ“Š Click [here](https://huggingface.co/spaces/huggingface/leaderboards) to view the results from your submission
267
+ """
268
+ )
269
+ else:
270
+ st.error("πŸ™ˆ Oh noes, there was an error submitting your submission!")
requirements.txt CHANGED
@@ -1,3 +1,3 @@
1
  huggingface-hub==0.4.0
2
  python-dotenv
3
- streamlit
 
1
  huggingface-hub==0.4.0
2
  python-dotenv
3
+ streamlit==1.2.0
utils.py CHANGED
@@ -1,6 +1,21 @@
 
 
1
  import requests
2
  from huggingface_hub import DatasetFilter, HfApi, ModelFilter
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  api = HfApi()
5
 
6
 
@@ -8,16 +23,23 @@ def get_auth_headers(token: str, prefix: str = "autonlp"):
8
  return {"Authorization": f"{prefix} {token}"}
9
 
10
 
11
- def http_post(
12
- path: str,
13
- token: str,
14
- payload=None,
15
- domain: str = None,
16
- ) -> requests.Response:
17
  """HTTP POST request to the AutoNLP API, raises UnreachableAPIError if the API cannot be reached"""
18
  try:
19
  response = requests.post(
20
- url=domain + path, json=payload, headers=get_auth_headers(token=token), allow_redirects=True
 
 
 
 
 
 
 
 
 
 
 
 
21
  )
22
  except requests.exceptions.ConnectionError:
23
  print("❌ Failed to reach AutoNLP API, check your internet connection")
@@ -25,13 +47,19 @@ def http_post(
25
  return response
26
 
27
 
28
- def get_metadata(dataset_name):
29
  filt = DatasetFilter(dataset_name=dataset_name)
30
  data = api.list_datasets(filter=filt, full=True)
31
- return data[0].cardData["train-eval-index"]
 
 
 
32
 
33
 
34
  def get_compatible_models(task, dataset_name):
35
- filt = ModelFilter(task=task, trained_dataset=dataset_name, library="transformers")
 
 
 
36
  compatible_models = api.list_models(filter=filt)
37
  return [model.modelId for model in compatible_models]
 
1
+ from typing import Dict, Union
2
+
3
  import requests
4
  from huggingface_hub import DatasetFilter, HfApi, ModelFilter
5
 
6
+ AUTOTRAIN_TASK_TO_HUB_TASK = {
7
+ "binary_classification": "text-classification",
8
+ "multi_class_classification": "text-classification",
9
+ # "multi_label_classification": "text-classification", # Not fully supported in AutoTrain
10
+ "entity_extraction": "token-classification",
11
+ "extractive_question_answering": "question-answering",
12
+ "translation": "translation",
13
+ "summarization": "summarization",
14
+ # "single_column_regression": 10,
15
+ }
16
+
17
+ HUB_TASK_TO_AUTOTRAIN_TASK = {v: k for k, v in AUTOTRAIN_TASK_TO_HUB_TASK.items()}
18
+
19
  api = HfApi()
20
 
21
 
 
23
  return {"Authorization": f"{prefix} {token}"}
24
 
25
 
26
+ def http_post(path: str, token: str, payload=None, domain: str = None, params=None) -> requests.Response:
 
 
 
 
 
27
  """HTTP POST request to the AutoNLP API, raises UnreachableAPIError if the API cannot be reached"""
28
  try:
29
  response = requests.post(
30
+ url=domain + path, json=payload, headers=get_auth_headers(token=token), allow_redirects=True, params=params
31
+ )
32
+ except requests.exceptions.ConnectionError:
33
+ print("❌ Failed to reach AutoNLP API, check your internet connection")
34
+ response.raise_for_status()
35
+ return response
36
+
37
+
38
+ def http_get(path: str, domain: str, token: str = None, params: dict = None) -> requests.Response:
39
+ """HTTP POST request to the AutoNLP API, raises UnreachableAPIError if the API cannot be reached"""
40
+ try:
41
+ response = requests.get(
42
+ url=domain + path, headers=get_auth_headers(token=token), allow_redirects=True, params=params
43
  )
44
  except requests.exceptions.ConnectionError:
45
  print("❌ Failed to reach AutoNLP API, check your internet connection")
 
47
  return response
48
 
49
 
50
+ def get_metadata(dataset_name: str) -> Union[Dict, None]:
51
  filt = DatasetFilter(dataset_name=dataset_name)
52
  data = api.list_datasets(filter=filt, full=True)
53
+ if data[0].cardData is not None and "train-eval-index" in data[0].cardData.keys():
54
+ return data[0].cardData["train-eval-index"]
55
+ else:
56
+ return None
57
 
58
 
59
  def get_compatible_models(task, dataset_name):
60
+ # TODO: relax filter on PyTorch models once supported in AutoTrain
61
+ filt = ModelFilter(
62
+ task=AUTOTRAIN_TASK_TO_HUB_TASK[task], trained_dataset=dataset_name, library=["transformers", "pytorch"]
63
+ )
64
  compatible_models = api.list_models(filter=filt)
65
  return [model.modelId for model in compatible_models]