lmspell 1.0.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.
Files changed (52) hide show
  1. lmspell/__init__.py +47 -0
  2. lmspell/core.py +26 -0
  3. lmspell/data/__init__.py +0 -0
  4. lmspell/data/lm_data_loader.py +43 -0
  5. lmspell/data/lm_spell_dataset.py +27 -0
  6. lmspell/exceptions.py +13 -0
  7. lmspell/model_enum.py +121 -0
  8. lmspell/models/__init__.py +0 -0
  9. lmspell/models/decoder_abstract.py +143 -0
  10. lmspell/models/encoder_abstract.py +171 -0
  11. lmspell/models/encoder_decoder_abstract.py +140 -0
  12. lmspell/models/gemma/__init__.py +0 -0
  13. lmspell/models/gemma/gemma2_9b_model.py +29 -0
  14. lmspell/models/gemma/gemma_abstract_model.py +22 -0
  15. lmspell/models/llama/__init__.py +0 -0
  16. lmspell/models/llama/llama31_8b_model.py +29 -0
  17. lmspell/models/llama/llama_abstract_model.py +22 -0
  18. lmspell/models/mbart/__init__.py +0 -0
  19. lmspell/models/mbart/language_codes.py +54 -0
  20. lmspell/models/mbart/mbart50_model.py +8 -0
  21. lmspell/models/mbart/mbart_abstract_model.py +39 -0
  22. lmspell/models/model_abstract.py +116 -0
  23. lmspell/models/mt5/__init__.py +0 -0
  24. lmspell/models/mt5/mt5_abstract_model.py +34 -0
  25. lmspell/models/mt5/mt5_base_model.py +8 -0
  26. lmspell/models/mt5/mt5_large_model.py +8 -0
  27. lmspell/models/sinbert/__init__.py +0 -0
  28. lmspell/models/sinbert/sinbert_abstract_model.py +17 -0
  29. lmspell/models/xlmr/__init__.py +0 -0
  30. lmspell/models/xlmr/xlmr_abstract_model.py +22 -0
  31. lmspell/models/xlmr/xlmr_large_model.py +8 -0
  32. lmspell/trainers/__init__.py +38 -0
  33. lmspell/trainers/abstract_seq_trainer.py +273 -0
  34. lmspell/trainers/abstract_trainer.py +12 -0
  35. lmspell/trainers/multi_gpu_trainer.py +122 -0
  36. lmspell/trainers/seq2seq_trainer.py +5 -0
  37. lmspell/trainers/seq_label_trainer.py +5 -0
  38. lmspell/trainers/unsloth_trainer.py +119 -0
  39. lmspell/trainers/utils/__init__.py +0 -0
  40. lmspell/trainers/utils/early_stopping.py +81 -0
  41. lmspell/trainers/utils/metrics.py +181 -0
  42. lmspell/utils/__init__.py +0 -0
  43. lmspell/utils/config_utils.py +40 -0
  44. lmspell/utils/dataset_utils.py +69 -0
  45. lmspell/utils/evaluate_utils.py +498 -0
  46. lmspell/utils/general_utils.py +19 -0
  47. lmspell/utils/google_utils.py +215 -0
  48. lmspell/utils/pre_post_processing_utils.py +33 -0
  49. lmspell-1.0.0.dist-info/METADATA +252 -0
  50. lmspell-1.0.0.dist-info/RECORD +52 -0
  51. lmspell-1.0.0.dist-info/WHEEL +4 -0
  52. lmspell-1.0.0.dist-info/licenses/LICENSE +395 -0
lmspell/__init__.py ADDED
@@ -0,0 +1,47 @@
1
+ """LMSpell: spell correction with pre-trained language models.
2
+
3
+ Heavy dependencies (torch, transformers, unsloth) are imported lazily, so
4
+ ``import lmspell`` stays cheap; they load on first use of ``LMSpell`` or
5
+ ``ModelEnum``.
6
+ """
7
+
8
+ from importlib import import_module
9
+ from typing import TYPE_CHECKING
10
+
11
+ from lmspell.exceptions import (
12
+ EvaluateModelException,
13
+ InvalidModelException,
14
+ LMSpellException,
15
+ )
16
+
17
+ __version__ = "1.0.0"
18
+
19
+ if TYPE_CHECKING:
20
+ from lmspell.core import LMSpell
21
+ from lmspell.model_enum import ModelEnum
22
+
23
+ __all__ = [
24
+ "LMSpell",
25
+ "ModelEnum",
26
+ "LMSpellException",
27
+ "InvalidModelException",
28
+ "EvaluateModelException",
29
+ "__version__",
30
+ ]
31
+
32
+ _LAZY_ATTRS = {
33
+ "LMSpell": "lmspell.core",
34
+ "ModelEnum": "lmspell.model_enum",
35
+ }
36
+
37
+
38
+ def __getattr__(name):
39
+ if name in _LAZY_ATTRS:
40
+ value = getattr(import_module(_LAZY_ATTRS[name]), name)
41
+ globals()[name] = value
42
+ return value
43
+ raise AttributeError(f"module 'lmspell' has no attribute {name!r}")
44
+
45
+
46
+ def __dir__():
47
+ return sorted(set(globals()) | set(__all__))
lmspell/core.py ADDED
@@ -0,0 +1,26 @@
1
+ from lmspell.exceptions import InvalidModelException
2
+ from lmspell.model_enum import ModelEnum
3
+
4
+
5
+ class LMSpell:
6
+
7
+ def __init__(self, model, language="Sinhala"):
8
+ """
9
+ Create a spell corrector for a supported Hugging Face model id (see ModelEnum).
10
+ `language` is the language name used in the LLM prompt and for the mBART language code.
11
+ """
12
+
13
+ try:
14
+ model_enum = ModelEnum(model)
15
+ except ValueError:
16
+ raise InvalidModelException(f"Invalid model: {model}") from None
17
+
18
+ model_class = model_enum.getModelClass()
19
+ self._model = model_class(model_label=model_enum, language=language)
20
+
21
+ def __getattr__(self, name):
22
+ # Only called for attributes LMSpell does not have; guard against recursion
23
+ # when _model itself is missing (e.g. during unpickling or a failed __init__).
24
+ if name == "_model":
25
+ raise AttributeError(name)
26
+ return getattr(self._model, name)
File without changes
@@ -0,0 +1,43 @@
1
+ from torch.utils.data import DataLoader
2
+
3
+ from lmspell.data.lm_spell_dataset import LMSpellDataset
4
+ from lmspell.utils.dataset_utils import DatasetUtils
5
+
6
+
7
+ class LMDataLoader:
8
+
9
+ def __init__(
10
+ self,
11
+ tokenizer,
12
+ dataset = LMSpellDataset,
13
+ train_path = None,
14
+ val_path = None,
15
+ test_path = None,
16
+ dataset_size = 1,
17
+ train_max_length = 128,
18
+ test_max_length = 128,
19
+ train_batch_size = 16,
20
+ test_batch_size = 16,
21
+ ):
22
+
23
+ self.dataset = dataset
24
+ self.train_dataloader = None
25
+ self.val_dataloader = None
26
+ self.test_dataloader = None
27
+
28
+ dataset = DatasetUtils(train_path=train_path, val_path=val_path, test_path=test_path, dataset_size=dataset_size)
29
+
30
+ if dataset.train_dataset is not None:
31
+ train_dataset = self.dataset(dataset.train_dataset, tokenizer, train_max_length)
32
+ train_dataloader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=False, pin_memory=True)
33
+ self.train_dataloader = train_dataloader
34
+
35
+ if dataset.val_dataset is not None:
36
+ val_dataset = self.dataset(dataset.val_dataset, tokenizer, train_max_length)
37
+ val_dataloader = DataLoader(val_dataset, batch_size=train_batch_size, shuffle=False, pin_memory=True)
38
+ self.val_dataloader = val_dataloader
39
+
40
+ if dataset.test_dataset is not None:
41
+ test_dataset = self.dataset(dataset.test_dataset, tokenizer, test_max_length)
42
+ test_dataloader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False, pin_memory=True)
43
+ self.test_dataloader = test_dataloader
@@ -0,0 +1,27 @@
1
+ import pandas as pd
2
+ from torch.utils.data import Dataset
3
+
4
+ from lmspell.utils.config_utils import ConfigUtils
5
+
6
+
7
+ class LMSpellDataset(Dataset):
8
+ def __init__(self, dataframe, tokenizer, max_length=128):
9
+ if not isinstance(dataframe, pd.DataFrame):
10
+ dataframe = pd.DataFrame(dataframe)
11
+ self.dataframe = dataframe
12
+ self.tokenizer = tokenizer
13
+ self.max_length = max_length
14
+
15
+ def __len__(self):
16
+ return len(self.dataframe)
17
+
18
+ def __getitem__(self, idx):
19
+ item = self.dataframe.iloc[idx]
20
+ dataset_columns = ConfigUtils.get_dataset_columns()
21
+ inputs = self.tokenizer(item[dataset_columns[0]], return_tensors='pt', padding='max_length', truncation=True, max_length=self.max_length)
22
+ targets = self.tokenizer(item[dataset_columns[1]], return_tensors='pt', padding='max_length', truncation=True, max_length=self.max_length)
23
+ inputs['labels'] = targets['input_ids']
24
+ return {key: val.squeeze() for key, val in inputs.items()}
25
+
26
+ def get(self):
27
+ return len(self.dataframe)
lmspell/exceptions.py ADDED
@@ -0,0 +1,13 @@
1
+ """Exceptions raised by LMSpell."""
2
+
3
+
4
+ class LMSpellException(Exception):
5
+ """Base class for all LMSpell errors."""
6
+
7
+
8
+ class InvalidModelException(LMSpellException):
9
+ """Raised when an unsupported model name is requested."""
10
+
11
+
12
+ class EvaluateModelException(LMSpellException):
13
+ """Raised when evaluation of a model fails."""
lmspell/model_enum.py ADDED
@@ -0,0 +1,121 @@
1
+ from enum import Enum
2
+ from typing import Dict, Type
3
+
4
+ from lmspell.models.model_abstract import ModelAbstract
5
+
6
+
7
+ class ModelEnum(Enum):
8
+ """
9
+ Enum class for all the models supported by the
10
+ Neural Spell Checker Library
11
+ """
12
+ # Fine-tuned models
13
+ # These checkpoints will be released upon the acceptance of the paper
14
+
15
+ # Llama 3.1 models
16
+ LLAMA_31_8B = "unsloth/Meta-Llama-3.1-8B-bnb-4bit"
17
+ LLAMA_31_8B_INSTRUCT = "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit"
18
+
19
+ # Llama 3.2 models
20
+ LLAMA_32_1B = "unsloth/Llama-3.2-1B-bnb-4bit"
21
+ LLAMA_32_1B_INSTRUCT = "unsloth/Llama-3.2-1B-Instruct-bnb-4bit"
22
+ LLAMA_32_3B = "unsloth/Llama-3.2-3B-bnb-4bit"
23
+ LLAMA_32_3B_INSTRUCT = "unsloth/Llama-3.2-3B-Instruct-bnb-4bit"
24
+
25
+ # Gemma 2 models
26
+ GEMMA_2_2B = "unsloth/gemma-2-2b-bnb-4bit"
27
+ GEMMA_2_2B_INSTRUCT = "unsloth/gemma-2-2b-it-bnb-4bit"
28
+ GEMMA_2_9B = "unsloth/gemma-2-9b-bnb-4bit"
29
+ GEMMA_2_9B_INSTRUCT = "unsloth/gemma-2-9b-it-bnb-4bit"
30
+
31
+ # MT5 models
32
+ MT5_SMALL = "google/mt5-small"
33
+ MT5_BASE = "google/mt5-base"
34
+ MT5_LARGE = "google/mt5-large"
35
+ MT5_XL = "google/mt5-xl"
36
+ MT5_XXL = "google/mt5-xxl"
37
+
38
+ # MBART models
39
+ MBART50 = "facebook/mbart-large-50"
40
+
41
+ # SinBERT models
42
+ SINBERT_SMALL = "NLPC-UOM/SinBERT-small"
43
+ SINBERT_LARGE = "NLPC-UOM/SinBERT-large"
44
+
45
+ # XLMR models
46
+ XLMR_BASE = "FacebookAI/xlm-roberta-base"
47
+ XLMR_LARGE = "FacebookAI/xlm-roberta-large"
48
+
49
+ def getModelClass(self) -> Type[ModelAbstract]:
50
+ """Return the model class associated with the enum value"""
51
+
52
+ from lmspell.models.gemma.gemma2_9b_model import Gemma29BModel
53
+ from lmspell.models.gemma.gemma_abstract_model import GemmaAbstractModel
54
+ from lmspell.models.llama.llama31_8b_model import Llama318BModel
55
+ from lmspell.models.llama.llama_abstract_model import LlamaAbstractModel
56
+ from lmspell.models.mbart.mbart50_model import Mbart50Model
57
+ from lmspell.models.mt5.mt5_abstract_model import Mt5AbstractModel
58
+ from lmspell.models.mt5.mt5_base_model import Mt5BaseModel
59
+ from lmspell.models.mt5.mt5_large_model import Mt5LargeModel
60
+ from lmspell.models.sinbert.sinbert_abstract_model import SinBertAbstractModel
61
+ from lmspell.models.xlmr.xlmr_abstract_model import XlmrAbstractModel
62
+ from lmspell.models.xlmr.xlmr_large_model import XlmrLargeModel
63
+
64
+ # import other model classes as needed
65
+
66
+ model_classes: Dict[ModelEnum, Type[ModelAbstract]] = {
67
+ # Fine-tuned models
68
+ # ModelEnum.LLAMA31_FT_SI: LlamaAbstractModel,
69
+ # ModelEnum.GEMMA2_FT_SI: GemmaAbstractModel,
70
+ # ModelEnum.MT5_FT_SI: Mt5AbstractModel,
71
+ # ModelEnum.MT5_XL_FT_SI: Mt5AbstractModel,
72
+ # ModelEnum.MBART_FT_SI: MbartAbstractModel,
73
+ # ModelEnum.SINBERT_FT_SI: SinBertAbstractModel,
74
+ # ModelEnum.XLMR_FT_SI: XlmrAbstractModel,
75
+
76
+ # Llama 3.1 models
77
+ ModelEnum.LLAMA_31_8B: Llama318BModel,
78
+ ModelEnum.LLAMA_31_8B_INSTRUCT: LlamaAbstractModel,
79
+
80
+ # Llama 3.2 models
81
+ ModelEnum.LLAMA_32_1B: LlamaAbstractModel,
82
+ ModelEnum.LLAMA_32_1B_INSTRUCT: LlamaAbstractModel,
83
+ ModelEnum.LLAMA_32_3B: LlamaAbstractModel,
84
+ ModelEnum.LLAMA_32_3B_INSTRUCT: LlamaAbstractModel,
85
+
86
+ # Gemma 2 models
87
+ ModelEnum.GEMMA_2_2B: GemmaAbstractModel,
88
+ ModelEnum.GEMMA_2_2B_INSTRUCT: GemmaAbstractModel,
89
+ ModelEnum.GEMMA_2_9B: Gemma29BModel,
90
+ ModelEnum.GEMMA_2_9B_INSTRUCT: GemmaAbstractModel,
91
+
92
+ # mT5 models
93
+ ModelEnum.MT5_SMALL: Mt5AbstractModel,
94
+ ModelEnum.MT5_BASE: Mt5BaseModel,
95
+ ModelEnum.MT5_LARGE: Mt5LargeModel,
96
+ ModelEnum.MT5_XL: Mt5AbstractModel,
97
+ ModelEnum.MT5_XXL: Mt5AbstractModel,
98
+
99
+ # mBART models
100
+ ModelEnum.MBART50: Mbart50Model,
101
+
102
+ # SinBERT models
103
+ ModelEnum.SINBERT_SMALL: SinBertAbstractModel,
104
+ ModelEnum.SINBERT_LARGE: SinBertAbstractModel,
105
+
106
+ # XLMR models
107
+ ModelEnum.XLMR_BASE: XlmrAbstractModel,
108
+ ModelEnum.XLMR_LARGE: XlmrLargeModel,
109
+
110
+ # Add other model-class mappings here
111
+ }
112
+ return model_classes[self]
113
+
114
+ def get_model_path(self) -> str:
115
+ """Return the model path"""
116
+ return self.value
117
+
118
+ @staticmethod
119
+ def get_supported_models():
120
+ """Return a list of all supported model names"""
121
+ return [model.value for model in ModelEnum]
File without changes
@@ -0,0 +1,143 @@
1
+ import pandas as pd
2
+
3
+ from lmspell.exceptions import LMSpellException
4
+ from lmspell.models.model_abstract import ModelAbstract
5
+ from lmspell.utils.config_utils import ConfigUtils
6
+ from lmspell.utils.evaluate_utils import EvaluateUtils
7
+
8
+
9
+ class DecoderAbstract(ModelAbstract):
10
+ do_sample: bool
11
+
12
+ def __init__(self, model_label=None, language="Sinhala"):
13
+ import torch
14
+ from unsloth import FastLanguageModel
15
+
16
+ super().__init__(model_label, language)
17
+
18
+ # Models mapped straight to LlamaAbstractModel/GemmaAbstractModel do not set do_sample;
19
+ # use the same default as the concrete Llama318BModel/Gemma29BModel.
20
+ if not hasattr(self, "do_sample"):
21
+ self.do_sample = True
22
+
23
+ self.max_seq_length = 2048 # WHY!!!
24
+ is_t4 = torch.cuda.is_available() and torch.cuda.get_device_name(0).startswith('Tesla T4')
25
+ self.dtype = torch.float16 if is_t4 else torch.bfloat16
26
+ self.load_in_4bit = True # Get as a parameter
27
+ self.attn_implementation = "flash_attention_2"
28
+
29
+ model, tokenizer = FastLanguageModel.from_pretrained(
30
+ model_name = self.model_label.get_model_path(),
31
+ max_seq_length = self.max_seq_length,
32
+ dtype = self.dtype,
33
+ load_in_4bit = self.load_in_4bit,
34
+ attn_implementation=self.attn_implementation,
35
+ random_state = self.seed,
36
+ )
37
+
38
+ # Need to check pros and cons of loading it like this for both training and inference.
39
+ model = FastLanguageModel.get_peft_model(
40
+ model,
41
+ r = 8, # 8, 16, 32, 64, 128 suggested
42
+ target_modules = ["q_proj", "k_proj", "v_proj", "o_proj","gate_proj", "up_proj", "down_proj"],
43
+ lora_alpha = 16,
44
+ lora_dropout = 0,
45
+ bias = "none",
46
+ use_gradient_checkpointing = "unsloth",
47
+ random_state = self.seed,
48
+ use_rslora = False,
49
+ loftq_config = None,
50
+ )
51
+
52
+ self.model = model
53
+ self.tokenizer = tokenizer
54
+
55
+ # pylint: disable-next=line-too-long
56
+ self.PROMPT = f"""You are an expert {self.language} spell corrector. Below is a sentence in {self.language} language. It may or may not have a spelling mistake. Give the corrected output in {self.language}.
57
+
58
+ ### Text:
59
+ {{}}
60
+
61
+ ### Output:
62
+ {{}}"""
63
+
64
+ def correct(self, input_set: list[str] | str | pd.DataFrame, target_set: list[str] | str | pd.DataFrame = None, output_dir: str = 'outputs'):
65
+ """
66
+ Corrects the text. The input set should be a string, a list of strings or a DataFrame.
67
+ Perform the evaluation if the target set is provided.
68
+ """
69
+ import os
70
+ import re
71
+
72
+ from tqdm import tqdm
73
+ from unsloth import FastLanguageModel
74
+
75
+ os.makedirs(os.path.join(os.getcwd(), output_dir), exist_ok=True)
76
+
77
+ input_set, evaluate_flag = self.process_input(input_set, target_set)
78
+
79
+ dataset_col_names = ConfigUtils.get_dataset_columns()
80
+
81
+ FastLanguageModel.for_inference(self.model)
82
+ pred_texts = []
83
+ generation_mode = self.model.generation_config.get_generation_mode()
84
+ print(f"Generation mode: {generation_mode}")
85
+
86
+ for src in tqdm(input_set[dataset_col_names[0]].to_list(), desc="Generating predictions"):
87
+ inputs = self.tokenizer(
88
+ [self.PROMPT.format(src, "")],
89
+ return_tensors="pt"
90
+ ).to(self.device)
91
+
92
+ outputs = self.model.generate(**inputs, max_new_tokens=self.max_seq_length, do_sample=self.do_sample, use_cache=True)
93
+
94
+ pred_text = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
95
+
96
+ pred_texts.append(pred_text)
97
+
98
+ output_texts = []
99
+ for pred in pred_texts:
100
+ match = re.search(r'### Output:\s*([^\n]+)', pred)
101
+ output_texts.append(match.group(1).strip() if match else None)
102
+
103
+ result_col_names = ConfigUtils.get_results_columns()
104
+
105
+ results_data = {
106
+ result_col_names[0]: input_set[dataset_col_names[0]].to_list(),
107
+ result_col_names[1]: output_texts
108
+ }
109
+
110
+ results_df = pd.DataFrame(results_data)
111
+
112
+ if evaluate_flag:
113
+ results_df[result_col_names[2]] = input_set[dataset_col_names[1]].to_list()
114
+ print("Evaluating the outputs...")
115
+ EvaluateUtils.evaluate_from_dataframe(results_df, output_dir)
116
+
117
+ return results_df
118
+
119
+ def correctFromFile(self, src: str, target: str = None, output_dir: str = "outputs"):
120
+ """
121
+ Corrects the text from a file. The file should be in the format of
122
+ a CSV or TXT file. If the file is a CSV, it should have two columns: 'text' and 'expected'.
123
+ If the file is a TXT, it should have one line per sentence. If target is provided,
124
+ it will be used for evaluation. The output will be saved in the specified output directory.
125
+ """
126
+
127
+ if src.endswith('.csv'):
128
+ df = pd.read_csv(src)
129
+ input_col, target_col = ConfigUtils.get_dataset_columns()
130
+ src = df[input_col].tolist()
131
+ if target_col in df.columns:
132
+ target = df[target_col].tolist()
133
+
134
+ elif src.endswith('.txt'):
135
+ with open(src, 'r', encoding='utf-8') as file:
136
+ src = file.readlines()
137
+ if target is not None:
138
+ with open(target, 'r', encoding='utf-8') as file:
139
+ target = file.readlines()
140
+ else:
141
+ raise LMSpellException("Unsupported file format. Only .csv and .txt are supported.") from None
142
+
143
+ return self.correct(src, target, output_dir)
@@ -0,0 +1,171 @@
1
+ import os
2
+ from typing import Any
3
+
4
+ import pandas as pd
5
+ import torch
6
+ from torch.utils.data import DataLoader
7
+ from tqdm import tqdm
8
+
9
+ from lmspell.data.lm_spell_dataset import LMSpellDataset
10
+ from lmspell.exceptions import LMSpellException
11
+ from lmspell.models.model_abstract import ModelAbstract
12
+ from lmspell.utils.config_utils import ConfigUtils
13
+ from lmspell.utils.evaluate_utils import EvaluateUtils
14
+ from lmspell.utils.general_utils import GeneralUtils
15
+ from lmspell.utils.pre_post_processing_utils import PrePostProcessingUtils
16
+
17
+
18
+ class EncoderAbstract(ModelAbstract):
19
+ accelerator: Any
20
+
21
+ def correct(self, input_set: list[str] | str | pd.DataFrame, target_set: list[str] | str | pd.DataFrame = None,
22
+ output_dir: str = "outputs", max_length: int = 128, batch_size: int = 8,
23
+ shuffle: bool = False): # pylint: disable=unused-argument # kept for API parity with EncoderDecoderAbstract
24
+ """
25
+ Corrects the text using encoder-only models (e.g., XLM-R). Supports string, list, or DataFrame as input.
26
+ If target_set is provided, performs evaluation and saves results.
27
+ """
28
+ os.makedirs(os.path.join(os.getcwd(), output_dir), exist_ok=True)
29
+
30
+ if max_length > 514:
31
+ print("Warning: max_length is set to 514, as it is the maximum length for XLMR models.")
32
+ max_length = 514
33
+
34
+ input_set, evaluate_flag = self.process_input(input_set, target_set)
35
+
36
+ dataset = LMSpellDataset(input_set, self.tokenizer, max_length)
37
+ dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False, pin_memory=True)
38
+
39
+ self.model = self.accelerator.prepare_model(self.model, evaluation_mode=True)
40
+ dataloader = self.accelerator.prepare_data_loader(dataloader)
41
+ self.model.resize_token_embeddings(len(self.tokenizer))
42
+ GeneralUtils.clean_memory()
43
+
44
+ self.model.eval()
45
+ originals, predictions, labels = [], [], []
46
+
47
+ with tqdm(dataloader, leave=True, disable=not self.accelerator.is_local_main_process) as pbar:
48
+ for batch in pbar:
49
+ with torch.no_grad():
50
+ outputs = self.model(input_ids=batch["input_ids"], attention_mask=batch["attention_mask"], labels=batch["labels"])
51
+
52
+ pred = torch.argmax(outputs.logits, dim=-1)
53
+ orig = batch["input_ids"]
54
+ label = batch["labels"]
55
+
56
+ gathered_orig, gathered_pred, gathered_label = self.accelerator.gather_for_metrics((orig, pred, label))
57
+ self.accelerator.wait_for_everyone()
58
+
59
+ originals.extend(gathered_orig)
60
+ predictions.extend(gathered_pred)
61
+ labels.extend(gathered_label)
62
+
63
+ results_df = self.decode(originals, predictions, labels, output_dir)
64
+
65
+ if evaluate_flag and self.accelerator.is_main_process:
66
+ eval_df = results_df.iloc[:, :2].copy()
67
+ if target_set is None:
68
+ # DataFrame input that carries its own target column.
69
+ target_col = input_set[ConfigUtils.get_dataset_columns()[1]]
70
+ elif isinstance(target_set, pd.DataFrame):
71
+ target_col = target_set.iloc[:, 0] if target_set.shape[1] > 0 else pd.Series([])
72
+ elif isinstance(target_set, list):
73
+ target_col = pd.Series(target_set)
74
+ elif isinstance(target_set, str):
75
+ target_col = pd.Series([target_set])
76
+ else:
77
+ raise LMSpellException("Cannot evaluate: target_set must be a string, a list of strings or a DataFrame.")
78
+
79
+ eval_df[ConfigUtils.get_results_columns()[2]] = target_col.reset_index(drop=True)
80
+
81
+ print("Evaluating the outputs...")
82
+ EvaluateUtils.evaluate_from_dataframe(eval_df, output_dir)
83
+
84
+ return results_df
85
+
86
+ def decode(self, originals, predictions, labels, output_dir):
87
+ """
88
+ Decode a tensor of tensors and save it to a file.
89
+ """
90
+
91
+ skip_special_tokens = False
92
+
93
+ # This is different in mbart model. Same for xlmr, sinbert and mt5. We don't need to do this in large models.
94
+ special_token_id_to_keep = self.tokenizer.convert_tokens_to_ids(PrePostProcessingUtils.ZWJ)
95
+ all_special_ids = torch.tensor(self.tokenizer.all_special_ids, dtype=torch.int64).to(self.device)
96
+
97
+ special_token_tensor = torch.tensor([special_token_id_to_keep], dtype=torch.int64).to(self.device)
98
+
99
+ originals, predictions, labels = [
100
+ [PrePostProcessingUtils.remove_special_tokens(tokens.to(self.device), all_special_ids, special_token_tensor) for tokens in data]
101
+ for data in (originals, predictions, labels)
102
+ ]
103
+
104
+ originals_decoded, predictions_decoded, labels_decoded = [
105
+ [self.tokenizer.decode(ids, skip_special_tokens=skip_special_tokens).replace('\n', '').strip() for ids in data]
106
+ for data in (originals, predictions, labels)
107
+ ]
108
+
109
+ result_col_names = ConfigUtils.get_results_columns()
110
+
111
+ results_data = {
112
+ result_col_names[0]: originals_decoded,
113
+ result_col_names[1]: predictions_decoded,
114
+ result_col_names[2]: labels_decoded,
115
+ }
116
+
117
+ # Write CSV inside the main process condition
118
+ results_df = pd.DataFrame(results_data)
119
+
120
+ # When using BERT models, these character were present in the output. They throws errors when saving in excel format
121
+ results_df = results_df.apply(lambda col: col.map(lambda x: x.replace('.\uffff', '').replace('\x11', '') if isinstance(x, str) else x))
122
+
123
+ print("Replacing ZWJ token")
124
+ for column in result_col_names:
125
+ results_df[column] = PrePostProcessingUtils.clean_zwj(results_df[column])
126
+
127
+ PrePostProcessingUtils.save_dataframe(results_df, output_dir)
128
+
129
+ return results_df
130
+
131
+ def correctFromFile(self, src: str, target: str = None, output_dir: str = "outputs", max_length: int = 128,
132
+ batch_size: int = 8, shuffle: bool = False):
133
+ """
134
+ Corrects the text from a file. The file should be in the format of
135
+ a CSV or TXT file. If the file is a CSV, it should have two columns: 'text' and 'expected'.
136
+ If the file is a TXT, it should have one line per sentence. If target is provided,
137
+ it will be used for evaluation. The output will be saved in the specified output directory.
138
+ """
139
+
140
+ if src.endswith('.csv'):
141
+ df = pd.read_csv(src)
142
+ input_col, target_col = ConfigUtils.get_dataset_columns()
143
+ src = df[input_col].tolist()
144
+ if target_col in df.columns:
145
+ target = df[target_col].tolist()
146
+
147
+ elif src.endswith('.txt'):
148
+ with open(src, 'r', encoding='utf-8') as file:
149
+ src = file.readlines()
150
+ if target is not None:
151
+ with open(target, 'r', encoding='utf-8') as file:
152
+ target = file.readlines()
153
+ else:
154
+ raise LMSpellException("Unsupported file format. Only .csv and .txt are supported.") from None
155
+
156
+ return self.correct(src, target, output_dir, max_length, batch_size, shuffle)
157
+
158
+ def load_model(self):
159
+ pass
160
+
161
+ def save_model(self):
162
+ pass
163
+
164
+ def get_model_name(self):
165
+ pass
166
+
167
+ def load_tokenizer(self):
168
+ pass
169
+
170
+ def save_tokenizer(self):
171
+ pass