flashrag-dev 0.1.1__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.
- flashrag/__init__.py +0 -0
- flashrag/config/__init__.py +2 -0
- flashrag/config/config.py +219 -0
- flashrag/dataset/__init__.py +2 -0
- flashrag/dataset/dataset.py +160 -0
- flashrag/dataset/utils.py +60 -0
- flashrag/evaluator/__init__.py +2 -0
- flashrag/evaluator/_bleu.py +209 -0
- flashrag/evaluator/evaluator.py +82 -0
- flashrag/evaluator/metrics.py +508 -0
- flashrag/evaluator/utils.py +19 -0
- flashrag/generator/__init__.py +2 -0
- flashrag/generator/fid.py +247 -0
- flashrag/generator/generator.py +623 -0
- flashrag/generator/openai_generator.py +96 -0
- flashrag/generator/stop_word_criteria.py +105 -0
- flashrag/judger/__init__.py +1 -0
- flashrag/judger/judger.py +184 -0
- flashrag/pipeline/__init__.py +3 -0
- flashrag/pipeline/active_pipeline.py +980 -0
- flashrag/pipeline/branching_pipeline.py +250 -0
- flashrag/pipeline/pipeline.py +248 -0
- flashrag/pipeline/replug_utils.py +249 -0
- flashrag/prompt/__init__.py +1 -0
- flashrag/prompt/base_prompt.py +165 -0
- flashrag/prompt/selfask_examplars.py +108 -0
- flashrag/prompt/trace_examplars.py +4015 -0
- flashrag/refiner/__init__.py +2 -0
- flashrag/refiner/kg_refiner.py +612 -0
- flashrag/refiner/llmlingua_compressor.py +2360 -0
- flashrag/refiner/refiner.py +252 -0
- flashrag/refiner/selective_context_compressor.py +294 -0
- flashrag/retriever/__init__.py +3 -0
- flashrag/retriever/__main__.py +4 -0
- flashrag/retriever/encoder.py +120 -0
- flashrag/retriever/index_builder.py +334 -0
- flashrag/retriever/reranker.py +145 -0
- flashrag/retriever/retriever.py +346 -0
- flashrag/retriever/utils.py +49 -0
- flashrag/utils/__init__.py +2 -0
- flashrag/utils/constants.py +51 -0
- flashrag/utils/pred_parse.py +25 -0
- flashrag/utils/utils.py +135 -0
- flashrag_dev-0.1.1.dist-info/LICENSE +21 -0
- flashrag_dev-0.1.1.dist-info/METADATA +616 -0
- flashrag_dev-0.1.1.dist-info/RECORD +48 -0
- flashrag_dev-0.1.1.dist-info/WHEEL +5 -0
- flashrag_dev-0.1.1.dist-info/top_level.txt +1 -0
flashrag/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import os
|
|
3
|
+
import yaml
|
|
4
|
+
import random
|
|
5
|
+
import datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Config:
|
|
9
|
+
def __init__(self, config_file_path=None, config_dict={}):
|
|
10
|
+
|
|
11
|
+
self.yaml_loader = self._build_yaml_loader()
|
|
12
|
+
self.file_config = self._load_file_config(config_file_path)
|
|
13
|
+
self.variable_config = config_dict
|
|
14
|
+
|
|
15
|
+
self.external_config = self._merge_external_config()
|
|
16
|
+
|
|
17
|
+
self.internal_config = self._get_internal_config()
|
|
18
|
+
|
|
19
|
+
self.final_config = self._get_final_config()
|
|
20
|
+
|
|
21
|
+
self._check_final_config()
|
|
22
|
+
self._set_additional_key()
|
|
23
|
+
|
|
24
|
+
self._init_device()
|
|
25
|
+
self._set_seed()
|
|
26
|
+
self._prepare_dir()
|
|
27
|
+
|
|
28
|
+
def _build_yaml_loader(self):
|
|
29
|
+
loader = yaml.FullLoader
|
|
30
|
+
loader.add_implicit_resolver(
|
|
31
|
+
"tag:yaml.org,2002:float",
|
|
32
|
+
re.compile(
|
|
33
|
+
"""^(?:
|
|
34
|
+
[-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
|
|
35
|
+
|[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
|
|
36
|
+
|\\.[0-9_]+(?:[eE][-+][0-9]+)?
|
|
37
|
+
|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*
|
|
38
|
+
|[-+]?\\.(?:inf|Inf|INF)
|
|
39
|
+
|\\.(?:nan|NaN|NAN))$""",
|
|
40
|
+
re.X,
|
|
41
|
+
),
|
|
42
|
+
list("-+0123456789."),
|
|
43
|
+
)
|
|
44
|
+
return loader
|
|
45
|
+
|
|
46
|
+
def _load_file_config(self, config_file_path: str):
|
|
47
|
+
file_config = dict()
|
|
48
|
+
if config_file_path:
|
|
49
|
+
with open(config_file_path, "r", encoding="utf-8") as f:
|
|
50
|
+
file_config.update(yaml.load(f.read(), Loader=self.yaml_loader))
|
|
51
|
+
return file_config
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _update_dict(old_dict: dict, new_dict: dict):
|
|
55
|
+
# Update the original update method of the dictionary:
|
|
56
|
+
# If there is the same key in `old_dict` and `new_dict`, and value is of type dict, update the key in dict
|
|
57
|
+
|
|
58
|
+
same_keys = []
|
|
59
|
+
for key, value in new_dict.items():
|
|
60
|
+
if key in old_dict and isinstance(value, dict):
|
|
61
|
+
same_keys.append(key)
|
|
62
|
+
for key in same_keys:
|
|
63
|
+
old_item = old_dict[key]
|
|
64
|
+
new_item = new_dict[key]
|
|
65
|
+
old_item.update(new_item)
|
|
66
|
+
new_dict[key] = old_item
|
|
67
|
+
|
|
68
|
+
old_dict.update(new_dict)
|
|
69
|
+
return old_dict
|
|
70
|
+
|
|
71
|
+
def _merge_external_config(self):
|
|
72
|
+
external_config = dict()
|
|
73
|
+
external_config = self._update_dict(external_config, self.file_config)
|
|
74
|
+
external_config = self._update_dict(external_config, self.variable_config)
|
|
75
|
+
|
|
76
|
+
return external_config
|
|
77
|
+
|
|
78
|
+
def _get_internal_config(self):
|
|
79
|
+
current_path = os.path.dirname(os.path.realpath(__file__))
|
|
80
|
+
init_config_path = os.path.join(current_path, "basic_config.yaml")
|
|
81
|
+
internal_config = self._load_file_config(init_config_path)
|
|
82
|
+
|
|
83
|
+
return internal_config
|
|
84
|
+
|
|
85
|
+
def _get_final_config(self):
|
|
86
|
+
final_config = dict()
|
|
87
|
+
final_config = self._update_dict(final_config, self.internal_config)
|
|
88
|
+
final_config = self._update_dict(final_config, self.external_config)
|
|
89
|
+
|
|
90
|
+
return final_config
|
|
91
|
+
|
|
92
|
+
def _check_final_config(self):
|
|
93
|
+
# check split
|
|
94
|
+
split = self.final_config["split"]
|
|
95
|
+
if split is None:
|
|
96
|
+
split = ["train", "dev", "test"]
|
|
97
|
+
if isinstance(split, str):
|
|
98
|
+
split = [split]
|
|
99
|
+
self.final_config["split"] = split
|
|
100
|
+
|
|
101
|
+
def _init_device(self):
|
|
102
|
+
gpu_id = self.final_config["gpu_id"]
|
|
103
|
+
if gpu_id is not None:
|
|
104
|
+
os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu_id)
|
|
105
|
+
import torch
|
|
106
|
+
|
|
107
|
+
self.final_config["device"] = torch.device("cuda")
|
|
108
|
+
else:
|
|
109
|
+
import torch
|
|
110
|
+
|
|
111
|
+
self.final_config["device"] = torch.device("cpu")
|
|
112
|
+
|
|
113
|
+
def _set_additional_key(self):
|
|
114
|
+
# set dataset
|
|
115
|
+
dataset_name = self.final_config["dataset_name"]
|
|
116
|
+
data_dir = self.final_config["data_dir"]
|
|
117
|
+
self.final_config["dataset_path"] = os.path.join(data_dir, dataset_name)
|
|
118
|
+
|
|
119
|
+
# set model path
|
|
120
|
+
retrieval_method = self.final_config["retrieval_method"]
|
|
121
|
+
model2path = self.final_config["model2path"]
|
|
122
|
+
model2pooling = self.final_config["model2pooling"]
|
|
123
|
+
method2index = self.final_config["method2index"]
|
|
124
|
+
|
|
125
|
+
generator_model = self.final_config["generator_model"]
|
|
126
|
+
|
|
127
|
+
if self.final_config["index_path"] is None:
|
|
128
|
+
try:
|
|
129
|
+
self.final_config["index_path"] = method2index[retrieval_method]
|
|
130
|
+
except:
|
|
131
|
+
print("Index is empty!!")
|
|
132
|
+
assert False
|
|
133
|
+
|
|
134
|
+
self.final_config["retrieval_model_path"] = model2path.get(retrieval_method, retrieval_method)
|
|
135
|
+
# TODO: not support when `retrieval_model` is path
|
|
136
|
+
|
|
137
|
+
def set_pooling_method(method, model2pooling):
|
|
138
|
+
for key, value in model2pooling.items():
|
|
139
|
+
if key.lower() in method.lower():
|
|
140
|
+
return value
|
|
141
|
+
return "mean"
|
|
142
|
+
|
|
143
|
+
if self.final_config.get("retrieval_pooling_method") is None:
|
|
144
|
+
self.final_config["retrieval_pooling_method"] = set_pooling_method(retrieval_method, model2pooling)
|
|
145
|
+
|
|
146
|
+
rerank_model_name = self.final_config["rerank_model_name"]
|
|
147
|
+
if self.final_config.get("rerank_model_path") is None:
|
|
148
|
+
if rerank_model_name is not None:
|
|
149
|
+
self.final_config["rerank_model_path"] = model2path.get(rerank_model_name, rerank_model_name)
|
|
150
|
+
if self.final_config["rerank_pooling_method"] is None:
|
|
151
|
+
if rerank_model_name is not None:
|
|
152
|
+
self.final_config["rerank_pooling_method"] = set_pooling_method(rerank_model_name, model2pooling)
|
|
153
|
+
|
|
154
|
+
if self.final_config.get("generator_model_path") is None:
|
|
155
|
+
self.final_config["generator_model_path"] = model2path.get(generator_model, generator_model)
|
|
156
|
+
|
|
157
|
+
if "refiner_name" in self.final_config:
|
|
158
|
+
refiner_model = self.final_config["refiner_name"]
|
|
159
|
+
if "refiner_model_path" not in self.final_config or self.final_config["refiner_model_path"] is None:
|
|
160
|
+
self.final_config["refiner_model_path"] = model2path.get(refiner_model, None)
|
|
161
|
+
|
|
162
|
+
# set model path in metric setting
|
|
163
|
+
metric_setting = self.final_config["metric_setting"]
|
|
164
|
+
metric_tokenizer_name = metric_setting.get("tokenizer_name", None)
|
|
165
|
+
from flashrag.utils.constants import OPENAI_MODEL_DICT
|
|
166
|
+
|
|
167
|
+
if metric_tokenizer_name not in OPENAI_MODEL_DICT:
|
|
168
|
+
metric_tokenizer_name = model2path.get(metric_tokenizer_name, metric_tokenizer_name)
|
|
169
|
+
metric_setting["tokenizer_name"] = metric_tokenizer_name
|
|
170
|
+
self.final_config["metric_setting"] = metric_setting
|
|
171
|
+
|
|
172
|
+
def _prepare_dir(self):
|
|
173
|
+
save_note = self.final_config["save_note"]
|
|
174
|
+
current_time = datetime.datetime.now()
|
|
175
|
+
self.final_config["save_dir"] = os.path.join(
|
|
176
|
+
self.final_config["save_dir"],
|
|
177
|
+
f"{self.final_config['dataset_name']}_{current_time.strftime('%Y_%m_%d_%H_%M')}_{save_note}",
|
|
178
|
+
)
|
|
179
|
+
os.makedirs(self.final_config["save_dir"], exist_ok=True)
|
|
180
|
+
# save config parameters
|
|
181
|
+
config_save_path = os.path.join(self.final_config["save_dir"], "config.yaml")
|
|
182
|
+
with open(config_save_path, "w") as f:
|
|
183
|
+
yaml.dump(self.final_config, f)
|
|
184
|
+
|
|
185
|
+
def _set_seed(self):
|
|
186
|
+
import torch
|
|
187
|
+
import numpy as np
|
|
188
|
+
|
|
189
|
+
seed = self.final_config["seed"]
|
|
190
|
+
random.seed(seed)
|
|
191
|
+
np.random.seed(seed)
|
|
192
|
+
torch.manual_seed(seed)
|
|
193
|
+
torch.cuda.manual_seed(seed)
|
|
194
|
+
torch.cuda.manual_seed_all(seed)
|
|
195
|
+
torch.backends.cudnn.benchmark = False
|
|
196
|
+
torch.backends.cudnn.deterministic = True
|
|
197
|
+
|
|
198
|
+
def __setitem__(self, key, value):
|
|
199
|
+
if not isinstance(key, str):
|
|
200
|
+
raise TypeError("index must be a str.")
|
|
201
|
+
self.final_config[key] = value
|
|
202
|
+
|
|
203
|
+
def __getattr__(self, item):
|
|
204
|
+
if "final_config" not in self.__dict__:
|
|
205
|
+
raise AttributeError("'Config' object has no attribute 'final_config'")
|
|
206
|
+
if item in self.final_config:
|
|
207
|
+
return self.final_config[item]
|
|
208
|
+
raise AttributeError(f"'Config' object has no attribute '{item}'")
|
|
209
|
+
|
|
210
|
+
def __getitem__(self, item):
|
|
211
|
+
return self.final_config.get(item)
|
|
212
|
+
|
|
213
|
+
def __contains__(self, key):
|
|
214
|
+
if not isinstance(key, str):
|
|
215
|
+
raise TypeError("index must be a str.")
|
|
216
|
+
return key in self.final_config
|
|
217
|
+
|
|
218
|
+
def __repr__(self):
|
|
219
|
+
return self.final_config.__str__()
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import random
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Item:
|
|
8
|
+
r"""A container class used to store and manipulate a sample within a dataset.
|
|
9
|
+
Information related to this sample during training/inference will be stored in ```self.output```.
|
|
10
|
+
Each attribute of this class can be used like a dict key(also for key in ```self.output```).
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, item_dict):
|
|
15
|
+
self.id = item_dict.get("id", None)
|
|
16
|
+
self.question = item_dict.get("question", None)
|
|
17
|
+
self.golden_answers = item_dict.get("golden_answers", [])
|
|
18
|
+
self.choices = item_dict.get("choices", [])
|
|
19
|
+
self.metadata = item_dict.get("metadata", {})
|
|
20
|
+
self.output = item_dict.get("output", {})
|
|
21
|
+
|
|
22
|
+
def update_output(self, key, value):
|
|
23
|
+
r"""Update the output dict and keep a key in self.output can be used as an attribute."""
|
|
24
|
+
if key in ["id", "question", "golden_answers", "output"]:
|
|
25
|
+
raise AttributeError(f"{key} should not be changed")
|
|
26
|
+
else:
|
|
27
|
+
self.output[key] = value
|
|
28
|
+
|
|
29
|
+
def update_evaluation_score(self, metric_name, metric_score):
|
|
30
|
+
r"""Update the evaluation score of this sample for a metric."""
|
|
31
|
+
if "metric_score" not in self.output:
|
|
32
|
+
self.output["metric_score"] = {}
|
|
33
|
+
self.output["metric_score"][metric_name] = metric_score
|
|
34
|
+
|
|
35
|
+
def __getattr__(self, attr_name):
|
|
36
|
+
if attr_name in ["id", "question", "golden_answers", "metadata", "output", "choices"]:
|
|
37
|
+
return super().__getattribute__(attr_name)
|
|
38
|
+
else:
|
|
39
|
+
output = super().__getattribute__("output")
|
|
40
|
+
if attr_name in output:
|
|
41
|
+
return output[attr_name]
|
|
42
|
+
else:
|
|
43
|
+
raise AttributeError(f"Attribute `{attr_name}` not found")
|
|
44
|
+
|
|
45
|
+
def to_dict(self):
|
|
46
|
+
r"""Convert all information within the data sample into a dict. Information generated
|
|
47
|
+
during the inference will be saved into output field.
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
for k, v in self.output.items():
|
|
51
|
+
if isinstance(k, np.ndarray):
|
|
52
|
+
self.output[k] = v.tolist()
|
|
53
|
+
output = {
|
|
54
|
+
"id": self.id,
|
|
55
|
+
"question": self.question,
|
|
56
|
+
"golden_answers": self.golden_answers,
|
|
57
|
+
"output": self.output,
|
|
58
|
+
}
|
|
59
|
+
if self.metadata != {}:
|
|
60
|
+
output["metadata"] = self.metadata
|
|
61
|
+
|
|
62
|
+
return output
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Dataset:
|
|
66
|
+
"""A container class used to store the whole dataset. Inside the class, each data sample will be stored
|
|
67
|
+
in ```Item``` class.
|
|
68
|
+
The properties of the dataset represent the list of attributes corresponding to each item in the dataset.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, config=None, dataset_path=None, data=None, sample_num=None, random_sample=False):
|
|
72
|
+
self.config = config
|
|
73
|
+
self.dataset_name = config["dataset_name"]
|
|
74
|
+
self.dataset_path = dataset_path
|
|
75
|
+
|
|
76
|
+
self.sample_num = sample_num
|
|
77
|
+
self.random_sample = random_sample
|
|
78
|
+
|
|
79
|
+
if data is None:
|
|
80
|
+
self.data = self._load_data(self.dataset_name, self.dataset_path)
|
|
81
|
+
else:
|
|
82
|
+
self.data = data
|
|
83
|
+
|
|
84
|
+
def _load_data(self, dataset_name, dataset_path):
|
|
85
|
+
"""Load data from the provided dataset_path or directly download the file(TODO)."""
|
|
86
|
+
|
|
87
|
+
if not os.path.exists(dataset_path):
|
|
88
|
+
# TODO: auto download: self._download(dataset_name, dataset_path)
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
data = []
|
|
92
|
+
with open(dataset_path, "r", encoding="utf-8") as f:
|
|
93
|
+
for line in f:
|
|
94
|
+
item_dict = json.loads(line)
|
|
95
|
+
item = Item(item_dict)
|
|
96
|
+
data.append(item)
|
|
97
|
+
if self.sample_num is not None:
|
|
98
|
+
if self.random_sample:
|
|
99
|
+
print(f"Random sample {self.sample_num} items in test set.")
|
|
100
|
+
data = random.sample(data, self.sample_num)
|
|
101
|
+
else:
|
|
102
|
+
data = data[: self.sample_num]
|
|
103
|
+
|
|
104
|
+
return data
|
|
105
|
+
|
|
106
|
+
def update_output(self, key, value_list):
|
|
107
|
+
"""Update the overall output field for each sample in the dataset."""
|
|
108
|
+
|
|
109
|
+
assert len(self.data) == len(value_list)
|
|
110
|
+
for item, value in zip(self.data, value_list):
|
|
111
|
+
item.update_output(key, value)
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def question(self):
|
|
115
|
+
return [item.question for item in self.data]
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def golden_answers(self):
|
|
119
|
+
return [item.golden_answers for item in self.data]
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def id(self):
|
|
123
|
+
return [item.id for item in self.data]
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def output(self):
|
|
127
|
+
return [item.output for item in self.data]
|
|
128
|
+
|
|
129
|
+
def get_batch_data(self, attr_name: str, batch_size: int):
|
|
130
|
+
"""Get an attribute of dataset items in batch."""
|
|
131
|
+
|
|
132
|
+
for i in range(0, len(self.data), batch_size):
|
|
133
|
+
batch_items = self.data[i : i + batch_size]
|
|
134
|
+
yield [item[attr_name] for item in batch_items]
|
|
135
|
+
|
|
136
|
+
def __getattr__(self, attr_name):
|
|
137
|
+
return [item.__getattr__(attr_name) for item in self.data]
|
|
138
|
+
|
|
139
|
+
def get_attr_data(self, attr_name):
|
|
140
|
+
"""For the attributes constructed later (not implemented using property),
|
|
141
|
+
obtain a list of this attribute in the entire dataset.
|
|
142
|
+
"""
|
|
143
|
+
return [item[attr_name] for item in self.data]
|
|
144
|
+
|
|
145
|
+
def __getitem__(self, index):
|
|
146
|
+
return self.data[index]
|
|
147
|
+
|
|
148
|
+
def __len__(self):
|
|
149
|
+
return len(self.data)
|
|
150
|
+
|
|
151
|
+
def save(self, save_path):
|
|
152
|
+
"""Save the dataset into the original format."""
|
|
153
|
+
|
|
154
|
+
def convert_to_float(d):
|
|
155
|
+
return {k: (v.item() if isinstance(v, np.generic) else v) for k, v in d.items()}
|
|
156
|
+
|
|
157
|
+
save_data = [convert_to_float(item.to_dict()) for item in self.data]
|
|
158
|
+
|
|
159
|
+
with open(save_path, "w") as f:
|
|
160
|
+
json.dump(save_data, f, indent=4)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from flashrag.dataset import Dataset
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def filter_dataset(dataset: Dataset, filter_func=None):
|
|
5
|
+
if filter_func is None:
|
|
6
|
+
return dataset
|
|
7
|
+
data = dataset.data
|
|
8
|
+
for item in data:
|
|
9
|
+
if not filter_func(item):
|
|
10
|
+
data.remove(item)
|
|
11
|
+
return Dataset(config=dataset.config, data=data)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def split_dataset(dataset: Dataset, split_symbol: list):
|
|
15
|
+
assert len(split_symbol) == len(dataset)
|
|
16
|
+
|
|
17
|
+
data = dataset.data
|
|
18
|
+
data_split = {symbol: [] for symbol in set(split_symbol)}
|
|
19
|
+
for symbol in set(split_symbol):
|
|
20
|
+
symbol_data = [
|
|
21
|
+
x for x, x_symbol in zip(data, split_symbol) if x_symbol == symbol
|
|
22
|
+
]
|
|
23
|
+
data_split[symbol] = Dataset(config=dataset.config, data=symbol_data)
|
|
24
|
+
|
|
25
|
+
return data_split
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def merge_dataset(dataset_split: dict, split_symbol: list):
|
|
29
|
+
assert len(split_symbol) == sum(
|
|
30
|
+
[len(data) for data in dataset_split.values()]
|
|
31
|
+
)
|
|
32
|
+
dataset_split_iter = {
|
|
33
|
+
symbol: iter(dataset.data) for symbol, dataset in dataset_split.items()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
final_data = []
|
|
37
|
+
for item_symbol in split_symbol:
|
|
38
|
+
final_data.append(next(dataset_split_iter[item_symbol]))
|
|
39
|
+
final_dataset = Dataset(
|
|
40
|
+
config=list(dataset_split.values())[0].config, data=final_data
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return final_dataset
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_batch_dataset(dataset: Dataset, batch_size=16):
|
|
47
|
+
data = dataset.data
|
|
48
|
+
for idx in range(0, len(data), batch_size):
|
|
49
|
+
batched_data = data[idx : idx + batch_size]
|
|
50
|
+
batch_dataset = Dataset(config=dataset.config, data=batched_data)
|
|
51
|
+
yield batch_dataset
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def merge_batch_dataset(dataset_list: Dataset):
|
|
55
|
+
dataset = dataset_list[0]
|
|
56
|
+
total_data = []
|
|
57
|
+
for batch_dataset in dataset_list:
|
|
58
|
+
total_data.extend(batch_dataset.data)
|
|
59
|
+
dataset = Dataset(config=dataset.config, data=total_data)
|
|
60
|
+
return dataset
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Source: https://github.com/mjpost/sacrebleu/blob/master/sacrebleu/tokenizers/tokenizer_13a.py
|
|
2
|
+
# Copyright 2020 SacreBLEU Authors.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
from functools import lru_cache
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseTokenizer:
|
|
21
|
+
"""A base dummy tokenizer to derive from."""
|
|
22
|
+
|
|
23
|
+
def signature(self):
|
|
24
|
+
"""
|
|
25
|
+
Returns a signature for the tokenizer.
|
|
26
|
+
:return: signature string
|
|
27
|
+
"""
|
|
28
|
+
return "none"
|
|
29
|
+
|
|
30
|
+
def __call__(self, line):
|
|
31
|
+
"""
|
|
32
|
+
Tokenizes an input line with the tokenizer.
|
|
33
|
+
:param line: a segment to tokenize
|
|
34
|
+
:return: the tokenized line
|
|
35
|
+
"""
|
|
36
|
+
return line
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TokenizerRegexp(BaseTokenizer):
|
|
40
|
+
def signature(self):
|
|
41
|
+
return "re"
|
|
42
|
+
|
|
43
|
+
def __init__(self):
|
|
44
|
+
self._re = [
|
|
45
|
+
# language-dependent part (assuming Western languages)
|
|
46
|
+
(re.compile(r"([\{-\~\[-\` -\&\(-\+\:-\@\/])"), r" \1 "),
|
|
47
|
+
# tokenize period and comma unless preceded by a digit
|
|
48
|
+
(re.compile(r"([^0-9])([\.,])"), r"\1 \2 "),
|
|
49
|
+
# tokenize period and comma unless followed by a digit
|
|
50
|
+
(re.compile(r"([\.,])([^0-9])"), r" \1 \2"),
|
|
51
|
+
# tokenize dash when preceded by a digit
|
|
52
|
+
(re.compile(r"([0-9])(-)"), r"\1 \2 "),
|
|
53
|
+
# one space only between words
|
|
54
|
+
# NOTE: Doing this in Python (below) is faster
|
|
55
|
+
# (re.compile(r'\s+'), r' '),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
@lru_cache(maxsize=2**16)
|
|
59
|
+
def __call__(self, line):
|
|
60
|
+
"""Common post-processing tokenizer for `13a` and `zh` tokenizers.
|
|
61
|
+
:param line: a segment to tokenize
|
|
62
|
+
:return: the tokenized line
|
|
63
|
+
"""
|
|
64
|
+
for _re, repl in self._re:
|
|
65
|
+
line = _re.sub(repl, line)
|
|
66
|
+
|
|
67
|
+
# no leading or trailing spaces, single space within words
|
|
68
|
+
# return ' '.join(line.split())
|
|
69
|
+
# This line is changed with regards to the original tokenizer (seen above) to return individual words
|
|
70
|
+
return line.split()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Tokenizer13a(BaseTokenizer):
|
|
74
|
+
def signature(self):
|
|
75
|
+
return "13a"
|
|
76
|
+
|
|
77
|
+
def __init__(self):
|
|
78
|
+
self._post_tokenizer = TokenizerRegexp()
|
|
79
|
+
|
|
80
|
+
@lru_cache(maxsize=2**16)
|
|
81
|
+
def __call__(self, line):
|
|
82
|
+
"""Tokenizes an input line using a relatively minimal tokenization
|
|
83
|
+
that is however equivalent to mteval-v13a, used by WMT.
|
|
84
|
+
:param line: a segment to tokenize
|
|
85
|
+
:return: the tokenized line
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
# language-independent part:
|
|
89
|
+
line = line.replace("<skipped>", "")
|
|
90
|
+
line = line.replace("-\n", "")
|
|
91
|
+
line = line.replace("\n", " ")
|
|
92
|
+
|
|
93
|
+
if "&" in line:
|
|
94
|
+
line = line.replace(""", '"')
|
|
95
|
+
line = line.replace("&", "&")
|
|
96
|
+
line = line.replace("<", "<")
|
|
97
|
+
line = line.replace(">", ">")
|
|
98
|
+
|
|
99
|
+
return self._post_tokenizer(f" {line} ")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Copyright 2017 Google Inc. All Rights Reserved.
|
|
103
|
+
#
|
|
104
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
105
|
+
# you may not use this file except in compliance with the License.
|
|
106
|
+
# You may obtain a copy of the License at
|
|
107
|
+
#
|
|
108
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
109
|
+
#
|
|
110
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
111
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
112
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
113
|
+
# See the License for the specific language governing permissions and
|
|
114
|
+
# limitations under the License.
|
|
115
|
+
# ==============================================================================
|
|
116
|
+
|
|
117
|
+
"""Python implementation of BLEU and smooth-BLEU.
|
|
118
|
+
|
|
119
|
+
This module provides a Python implementation of BLEU and smooth-BLEU.
|
|
120
|
+
Smooth BLEU is computed following the method outlined in the paper:
|
|
121
|
+
Chin-Yew Lin, Franz Josef Och. ORANGE: a method for evaluating automatic
|
|
122
|
+
evaluation metrics for machine translation. COLING 2004.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
import collections
|
|
126
|
+
import math
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _get_ngrams(segment, max_order):
|
|
130
|
+
"""Extracts all n-grams upto a given maximum order from an input segment.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
segment: text segment from which n-grams will be extracted.
|
|
134
|
+
max_order: maximum length in tokens of the n-grams returned by this
|
|
135
|
+
methods.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The Counter containing all n-grams upto max_order in segment
|
|
139
|
+
with a count of how many times each n-gram occurred.
|
|
140
|
+
"""
|
|
141
|
+
ngram_counts = collections.Counter()
|
|
142
|
+
for order in range(1, max_order + 1):
|
|
143
|
+
for i in range(0, len(segment) - order + 1):
|
|
144
|
+
ngram = tuple(segment[i : i + order])
|
|
145
|
+
ngram_counts[ngram] += 1
|
|
146
|
+
return ngram_counts
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def compute_bleu(reference_corpus, translation_corpus, max_order=4, smooth=False):
|
|
150
|
+
"""Computes BLEU score of translated segments against one or more references.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
reference_corpus: list of lists of references for each translation. Each
|
|
154
|
+
reference should be tokenized into a list of tokens.
|
|
155
|
+
translation_corpus: list of translations to score. Each translation
|
|
156
|
+
should be tokenized into a list of tokens.
|
|
157
|
+
max_order: Maximum n-gram order to use when computing BLEU score.
|
|
158
|
+
smooth: Whether or not to apply Lin et al. 2004 smoothing.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
3-Tuple with the BLEU score, n-gram precisions, geometric mean of n-gram
|
|
162
|
+
precisions and brevity penalty.
|
|
163
|
+
"""
|
|
164
|
+
matches_by_order = [0] * max_order
|
|
165
|
+
possible_matches_by_order = [0] * max_order
|
|
166
|
+
reference_length = 0
|
|
167
|
+
translation_length = 0
|
|
168
|
+
for references, translation in zip(reference_corpus, translation_corpus):
|
|
169
|
+
reference_length += min(len(r) for r in references)
|
|
170
|
+
translation_length += len(translation)
|
|
171
|
+
|
|
172
|
+
merged_ref_ngram_counts = collections.Counter()
|
|
173
|
+
for reference in references:
|
|
174
|
+
merged_ref_ngram_counts |= _get_ngrams(reference, max_order)
|
|
175
|
+
translation_ngram_counts = _get_ngrams(translation, max_order)
|
|
176
|
+
overlap = translation_ngram_counts & merged_ref_ngram_counts
|
|
177
|
+
for ngram in overlap:
|
|
178
|
+
matches_by_order[len(ngram) - 1] += overlap[ngram]
|
|
179
|
+
for order in range(1, max_order + 1):
|
|
180
|
+
possible_matches = len(translation) - order + 1
|
|
181
|
+
if possible_matches > 0:
|
|
182
|
+
possible_matches_by_order[order - 1] += possible_matches
|
|
183
|
+
|
|
184
|
+
precisions = [0] * max_order
|
|
185
|
+
for i in range(0, max_order):
|
|
186
|
+
if smooth:
|
|
187
|
+
precisions[i] = (matches_by_order[i] + 1.0) / (possible_matches_by_order[i] + 1.0)
|
|
188
|
+
else:
|
|
189
|
+
if possible_matches_by_order[i] > 0:
|
|
190
|
+
precisions[i] = float(matches_by_order[i]) / possible_matches_by_order[i]
|
|
191
|
+
else:
|
|
192
|
+
precisions[i] = 0.0
|
|
193
|
+
|
|
194
|
+
if min(precisions) > 0:
|
|
195
|
+
p_log_sum = sum((1.0 / max_order) * math.log(p) for p in precisions)
|
|
196
|
+
geo_mean = math.exp(p_log_sum)
|
|
197
|
+
else:
|
|
198
|
+
geo_mean = 0
|
|
199
|
+
|
|
200
|
+
ratio = float(translation_length) / reference_length
|
|
201
|
+
|
|
202
|
+
if ratio > 1.0:
|
|
203
|
+
bp = 1.0
|
|
204
|
+
else:
|
|
205
|
+
bp = math.exp(1 - 1.0 / ratio)
|
|
206
|
+
|
|
207
|
+
bleu = geo_mean * bp
|
|
208
|
+
|
|
209
|
+
return (bleu, precisions, bp, ratio, translation_length, reference_length)
|