Skip to content

Commit d299743

Browse files
committed
Merge branch 'dev'
2 parents 8b3959e + b4aa232 commit d299743

10 files changed

Lines changed: 2745 additions & 2262 deletions

File tree

docs/reference.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ NLTKNamedEntityRecognizer
6464
:members:
6565

6666

67+
Retrieval
68+
---------
69+
70+
.. automodule:: renard.pipeline.ner.retrieval
71+
:members:
72+
73+
6774
Coreference Resolution
6875
======================
6976

pyproject.toml

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
[project]
22
name = "renard-pipeline"
3-
version = "0.7.1"
3+
version = "0.7.2"
44
description = "Relationships Extraction from NARrative Documents"
55
authors = [
66
{name = "Arthur Amalvy", email = "arthur.amalvy@univ-avignon.fr"},
77
]
88
license = { text = "GPL-3.0-only" }
99
readme = "README.md"
10-
requires-python = ">=3.9,<3.13"
10+
requires-python = ">=3.10,<3.14"
1111
dependencies = [
12-
"torch>=2.7.0",
13-
"transformers>=4.57.1",
14-
"nltk>=3.9.1",
12+
"torch>=2.8.0",
13+
"transformers>=5.0.0",
14+
"nltk>=3.10.3",
1515
"tqdm>=4.67.1",
1616
"networkx>=3.2",
1717
"more-itertools>=10.7",
1818
"nameparser>=1.1.3",
1919
"matplotlib>=3.9",
2020
"pytest>=8.4.1",
21-
"tibert>=0.5.2",
21+
"tibert>=0.5.3",
2222
"grimbert>=0.1.5",
2323
"datasets>=4.0.0",
2424
"rank-bm25>=0.2.2",
2525
"accelerate>=1.10.1",
2626
"scikit-learn>=1.6.1",
2727
"tiktoken>=0.12.0",
28-
"protobuf>=6.33.2",
28+
"protobuf>=6.33.5",
2929
]
3030

3131
[build-system]
@@ -50,44 +50,67 @@ dev = [
5050

5151
[project.optional-dependencies]
5252
ui = [
53-
"gradio>=4.44.1",
53+
"gradio>=6.0.0",
5454
"pyvis>=0.3.2",
5555
]
5656
cpu = [
57-
"torch>=2.7.1",
57+
"torch>=2.8.0",
5858
]
5959
cuda128 = [
60-
"torch>=2.7.1",
60+
"torch>=2.8.0",
61+
]
62+
cuda129 = [
63+
"torch>=2.8.0",
6164
]
6265
rocm63 = [
63-
"torch>=2.7.1",
64-
"pytorch-triton-rocm>=3.1.0",
66+
"torch>=2.8.0",
67+
"triton-rocm",
68+
"pytorch-triton-rocm>=3.5.0 ; sys_platform == 'linux'",
6569
]
6670
rocm64 = [
67-
"torch>=2.7.1",
68-
"pytorch-triton-rocm>=3.1.0",
71+
"torch>=2.8.0",
72+
"triton-rocm",
73+
"pytorch-triton-rocm>=3.5.0 ; sys_platform == 'linux'",
74+
]
75+
rocm71 = [
76+
"torch>=2.8.0",
77+
"triton-rocm",
78+
"pytorch-triton-rocm>=3.1.0 ; sys_platform == 'linux'",
79+
]
80+
dspy = [
81+
"dspy>=2.6.27",
6982
]
7083

7184
[tool.uv]
7285
conflicts = [
7386
[
7487
{ extra = "cpu" },
7588
{ extra = "cuda128" },
89+
{ extra = "cuda129" },
7690
{ extra = "rocm63" },
7791
{ extra = "rocm64" },
92+
{ extra = "rocm71" },
7893
],
7994
]
8095

8196
[tool.uv.sources]
8297
torch = [
8398
{ index = "pytorch-cpu", extra = "cpu" },
8499
{ index = "pytorch-cuda128", extra = "cuda128" },
85-
{ index = "pytorch-rocm63", extra = "rocm63" },
86-
{ index = "pytorch-rocm64", extra = "rocm64" },
100+
{ index = "pytorch-cuda129", extra = "cuda129" },
101+
{ index = "pytorch-rocm63", extra = "rocm63", marker = "sys_platform == 'linux'" },
102+
{ index = "pytorch-rocm64", extra = "rocm64", marker = "sys_platform == 'linux'" },
103+
{ index = "pytorch-rocm71", extra = "rocm71", marker = "sys_platform == 'linux'" },
87104
]
88105
pytorch-triton-rocm = [
89-
{ index = "pytorch-rocm63", extra = "rocm63" },
90-
{ index = "pytorch-rocm64", extra = "rocm64" },
106+
{ index = "pytorch-rocm63", extra = "rocm63", marker = "sys_platform == 'linux'" },
107+
{ index = "pytorch-rocm64", extra = "rocm64", marker = "sys_platform == 'linux'" },
108+
{ index = "pytorch-rocm71", extra = "rocm71", marker = "sys_platform == 'linux'" },
109+
]
110+
triton-rocm = [
111+
{ index = "pytorch-rocm63", extra = "rocm63", marker = "sys_platform == 'linux'" },
112+
{ index = "pytorch-rocm64", extra = "rocm64", marker = "sys_platform == 'linux'" },
113+
{ index = "pytorch-rocm71", extra = "rocm71", marker = "sys_platform == 'linux'" },
91114
]
92115

93116
[[tool.uv.index]]
@@ -100,6 +123,11 @@ name = "pytorch-cuda128"
100123
url = "https://download.pytorch.org/whl/cu128"
101124
explicit = true
102125

126+
[[tool.uv.index]]
127+
name = "pytorch-cuda129"
128+
url = "https://download.pytorch.org/whl/cu129"
129+
explicit = true
130+
103131
[[tool.uv.index]]
104132
name = "pytorch-rocm63"
105133
url = "https://download.pytorch.org/whl/rocm6.3"
@@ -108,4 +136,9 @@ explicit = true
108136
[[tool.uv.index]]
109137
name = "pytorch-rocm64"
110138
url = "https://download.pytorch.org/whl/rocm6.4"
139+
explicit = true
140+
141+
[[tool.uv.index]]
142+
name = "pytorch-rocm71"
143+
url = "https://download.pytorch.org/whl/rocm7.1"
111144
explicit = true

renard/ner_utils.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from more_itertools import flatten
77
import torch
88
from torch.utils.data import Dataset
9-
from datasets import Dataset as HGDataset
9+
from datasets import Dataset as HFDataset, DatasetDict as HFDatasetDict
1010
from datasets import Sequence, ClassLabel
1111
from transformers import (
1212
AutoModelForTokenClassification,
@@ -92,7 +92,7 @@ def __init__(
9292
assert all(
9393
[len(cm) == len(elt) for elt, cm in zip(self.elements, context_mask)]
9494
)
95-
self._context_mask = context_mask or [[1] * len(elt) for elt in self.elements]
95+
self._context_mask = context_mask or [[0] * len(elt) for elt in self.elements]
9696

9797
self.tokenizer = tokenizer
9898

@@ -213,8 +213,10 @@ def load_conll2002_bio(
213213
tags = []
214214
for line in raw_data.split("\n"):
215215
line = line.strip("\n")
216-
if re.fullmatch(r"\s*", line) or (
217-
not max_sent_len is None and len(sent_tokens) >= max_sent_len
216+
if (
217+
re.fullmatch(r"\s*", line) # ignore empty lines
218+
or re.fullmatch(r"# [^:]+: .*", line) # ignore Novelties style metadata
219+
or (not max_sent_len is None and len(sent_tokens) >= max_sent_len)
218220
):
219221
if len(sent_tokens) == 0:
220222
continue
@@ -224,29 +226,36 @@ def load_conll2002_bio(
224226
token, tag = line.split(separator)
225227
sent_tokens.append(token)
226228
tags.append(tag_conversion_map.get(tag, tag))
229+
if len(sent_tokens) != 0:
230+
sents.append(sent_tokens)
227231

228232
tokens = list(flatten(sents))
229233
entities = ner_entities(tokens, tags)
230234

231235
return sents, list(flatten(sents)), entities
232236

233237

234-
def hgdataset_from_conll2002(
238+
def hfdataset_from_conll2002(
235239
path: str,
236240
tag_conversion_map: Optional[Dict[str, str]] = None,
237241
separator: str = "\t",
238242
max_sent_len: Optional[int] = None,
243+
labels: Optional[list[str]] = None,
239244
**kwargs,
240-
) -> HGDataset:
245+
) -> HFDataset:
241246
"""Load a CoNLL-2002 file as a Huggingface Dataset.
242247
243248
:param path: passed to :func:`.load_conll2002_bio`
244249
:param tag_conversion_map: passed to :func:`load_conll2002_bio`
245250
:param separator: passed to :func:`load_conll2002_bio`
246251
:param max_sent_len: passed to :func:`load_conll2002_bio`
252+
:param labels: the list of all possible labels. If ``None``, will
253+
automatically be assigned to the sorted list of possible tags
254+
found in the input file.
247255
:param kwargs: additional kwargs for :func:`open`
248256
249-
:return: a :class:`datasets.Dataset` with features 'tokens' and 'labels'.
257+
:return: a :class:`datasets.Dataset` with features 'tokens' and
258+
'labels'.
250259
"""
251260
sentences, tokens, entities = load_conll2002_bio(
252261
path, tag_conversion_map, separator, max_sent_len, **kwargs
@@ -268,13 +277,21 @@ def hgdataset_from_conll2002(
268277
for sent_start, sent_end in zip(sent_starts, sent_ends)
269278
]
270279

271-
dataset = HGDataset.from_dict({"tokens": sentences, "labels": sent_tags})
272-
dataset = dataset.cast_column(
273-
"labels", Sequence(ClassLabel(names=sorted(set(tags))))
274-
)
280+
dataset = HFDataset.from_dict({"tokens": sentences, "labels": sent_tags})
281+
if labels is None:
282+
labels = sorted(set(tags))
283+
dataset = dataset.cast_column("labels", Sequence(ClassLabel(names=labels)))
275284
return dataset
276285

277286

287+
def hgdataset_from_conll2002(**kwargs) -> HFDataset:
288+
"""
289+
Deprecated function that only exists for retrocompatibility, you
290+
should call :func:`.hfdataset_from_conll2002` instead.
291+
"""
292+
return hfdataset_from_conll2002(**kwargs)
293+
294+
278295
def _tokenize_and_align_labels(
279296
examples, tokenizer: PreTrainedTokenizerFast, label_all_tokens: bool = True
280297
):
@@ -315,37 +332,49 @@ def _tokenize_and_align_labels(
315332

316333

317334
def train_ner_model(
318-
hg_id: str,
319-
dataset: HGDataset,
335+
hf_id: str,
336+
dataset: Union[HFDataset, HFDatasetDict],
320337
targs: TrainingArguments,
338+
train_split: str = "train",
339+
valid_split: str = "valid",
340+
trainer_class: type[Trainer] = Trainer,
321341
) -> PreTrainedModel:
342+
"""Train a NER model on the given dataset.
343+
344+
:param hf_id: huggingface ID of the model to train
345+
:param dataset: huggingface dataset on which to train. The
346+
'labels' column is assumed to contain NER labels.
347+
:param TrainingArguments: training arguments for the huggingface
348+
trainer.
349+
:param train_split: split of the dataset used for train.
350+
:param valid_split: split of the dataset used for validation.
351+
:param trainer_class: trainer class to use. Can be used to
352+
override the default huggingface trainer.
353+
"""
322354
from transformers import DataCollatorForTokenClassification
323355

324356
# BERT tokenizer splits tokens into subtokens. The
325357
# tokenize_and_align_labels function correctly aligns labels and
326358
# subtokens.
327-
tokenizer = AutoTokenizer.from_pretrained(hg_id)
359+
tokenizer = AutoTokenizer.from_pretrained(hf_id)
328360
dataset = dataset.map(
329361
ft.partial(_tokenize_and_align_labels, tokenizer=tokenizer), batched=True
330362
)
331-
dataset = dataset.train_test_split(test_size=0.1)
332363

333-
label_lst = dataset["train"].features["labels"].feature.names
364+
label_lst = dataset[train_split].features["labels"].feature.names
334365
model = AutoModelForTokenClassification.from_pretrained(
335-
hg_id,
366+
hf_id,
336367
num_labels=len(label_lst),
337368
id2label={i: label for i, label in enumerate(label_lst)},
338369
label2id={label: i for i, label in enumerate(label_lst)},
339370
)
340371

341-
trainer = Trainer(
372+
trainer = trainer_class(
342373
model,
343374
targs,
344-
train_dataset=dataset["train"],
345-
eval_dataset=dataset["test"],
346-
# data_collator=DataCollatorForTokenClassificationWithBatchEncoding(tokenizer),
375+
train_dataset=dataset[train_split],
376+
eval_dataset=dataset[valid_split],
347377
data_collator=DataCollatorForTokenClassification(tokenizer),
348-
tokenizer=tokenizer,
349378
)
350379
trainer.train()
351380

renard/pipeline/core.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def __init__(self):
8282

8383
def _pipeline_init_(
8484
self, lang: str, progress_reporter: ProgressReporter, **kwargs
85-
) -> Optional[Dict[Pipeline.PipelineParameter, Any]]:
85+
) -> Optional[Dict[str, Any]]:
8686
"""Set the step configuration that is common to the whole
8787
pipeline.
8888
@@ -576,16 +576,13 @@ def update(slider_value):
576576
class Pipeline:
577577
"""A flexible NLP pipeline"""
578578

579-
#: all the possible parameters of the whole pipeline, that are
580-
#: shared between steps
581-
PipelineParameter = Literal["lang", "progress_reporter", "character_ner_tag"]
582-
583579
def __init__(
584580
self,
585581
steps: List[PipelineStep],
586582
lang: str = "eng",
587583
progress_report: Optional[Literal["tqdm"]] = "tqdm",
588584
warn: bool = True,
585+
**step_additional_params,
589586
) -> None:
590587
"""
591588
:param steps: a ``tuple`` of :class:``PipelineStep``, that
@@ -595,16 +592,28 @@ def __init__(
595592
progress.
596593
:param lang: ISO 639-3 language code
597594
:param warn:
595+
596+
:param step_additional_params: additional parameters passed to
597+
:meth:`._pipeline_init_` when
598+
:meth:`_pipeline_init_steps_` is called. The following
599+
values are currently used:
600+
601+
- ``'character_ner_tag'``: the NER tag corresponding
602+
to characters (default: ``PER``)
598603
"""
599604
self.steps = steps
600605

601606
self.progress_report: Optional[Literal["tqdm"]] = progress_report
602607
self.progress_reporter = get_progress_reporter(progress_report)
603608

604609
self.lang = lang
605-
self.character_ner_tag = "PER"
606610
self.warn = warn
607611

612+
self.step_additional_params = step_additional_params
613+
self.step_additional_params["character_ner_tag"] = (
614+
self.step_additional_params.get("character_ner_tag", "PER")
615+
)
616+
608617
def _pipeline_init_steps_(self, ignored_steps: Optional[List[str]] = None):
609618
"""Initialise steps with global pipeline parameters.
610619
@@ -613,16 +622,18 @@ def _pipeline_init_steps_(self, ignored_steps: Optional[List[str]] = None):
613622
"""
614623
steps_progress_reporter = self.progress_reporter.get_subreporter()
615624
steps = self._non_ignored_steps(ignored_steps)
616-
pipeline_params = {
617-
"progress_reporter": steps_progress_reporter,
618-
"character_ner_tag": self.character_ner_tag,
619-
}
625+
pipeline_params = self.step_additional_params.copy()
620626
for step in steps:
621-
step_additional_params = step._pipeline_init_(self.lang, **pipeline_params)
627+
step_additional_params = step._pipeline_init_(
628+
self.lang, progress_reporter=steps_progress_reporter, **pipeline_params
629+
)
622630
if not step_additional_params is None:
623631
for key, value in step_additional_params.items():
624632
setattr(self, key, value)
625-
pipeline_params[key] = value
633+
# parameters set by the user have precedence over
634+
# step mandated parameters
635+
if not key in self.step_additional_params:
636+
pipeline_params[key] = value
626637

627638
def _non_ignored_steps(
628639
self, ignored_steps: Optional[List[str]]

0 commit comments

Comments
 (0)