granuscore 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.
- granuscore/__init__.py +13 -0
- granuscore/artifcats.py +116 -0
- granuscore/bucket_output.py +53 -0
- granuscore/cache.py +189 -0
- granuscore/claim_splitter.py +300 -0
- granuscore/default_assets.py +38 -0
- granuscore/granularity_predictor.py +796 -0
- granuscore/loader.py +118 -0
- granuscore/pipeline.py +562 -0
- granuscore/text_cleaner.py +61 -0
- granuscore/utils.py +48 -0
- granuscore-1.0.0.dist-info/METADATA +242 -0
- granuscore-1.0.0.dist-info/RECORD +16 -0
- granuscore-1.0.0.dist-info/WHEEL +5 -0
- granuscore-1.0.0.dist-info/licenses/LICENSE +21 -0
- granuscore-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TextCleaner:
|
|
5
|
+
"""Utilities for cleaning and normalizing text."""
|
|
6
|
+
|
|
7
|
+
# Regex patterns
|
|
8
|
+
LATEX_PATTERN = re.compile(
|
|
9
|
+
r"<\s*l\s*a\s*t\s*e\s*x\s*i\s*t(?:\s+[^>]*)?>.*?<\s*/\s*l\s*a\s*t\s*e\s*x\s*i\s*t\s*>",
|
|
10
|
+
re.DOTALL | re.IGNORECASE,
|
|
11
|
+
)
|
|
12
|
+
ARXIV_META_PATTERN = re.compile(r"arXiv:\d+\.\d+v\d+", re.IGNORECASE)
|
|
13
|
+
FIGURE_CAPTION_PATTERN = re.compile(r"(Figure|Fig\.)\s*\d+[:].*", re.IGNORECASE)
|
|
14
|
+
BRACKETS_PATTERN = re.compile(r"\([^)]*\)|\[[^\]]*\]")
|
|
15
|
+
SINGLE_LETTER_NOISE_PATTERN = re.compile(r"(?:\b[A-Za-z0-9]\b\s+){2,}\b[A-Za-z0-9]\b")
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def clean(
|
|
19
|
+
cls,
|
|
20
|
+
text: str,
|
|
21
|
+
*,
|
|
22
|
+
skip_brackets: bool = False,
|
|
23
|
+
skip_latex: bool = False,
|
|
24
|
+
skip_arxiv_meta: bool = False,
|
|
25
|
+
skip_figures: bool = False,
|
|
26
|
+
skip_pdf_noise: bool = False,
|
|
27
|
+
) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Clean text by removing various unwanted patterns.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
text: Input text to clean
|
|
33
|
+
skip_brackets: Remove content in brackets/parentheses
|
|
34
|
+
skip_latex: Remove LaTeX blocks
|
|
35
|
+
skip_arxiv_meta: Remove arXiv metadata
|
|
36
|
+
skip_figures: Remove figure captions
|
|
37
|
+
skip_pdf_noise: Remove PDF extraction noise
|
|
38
|
+
Returns:
|
|
39
|
+
Cleaned text
|
|
40
|
+
"""
|
|
41
|
+
if skip_latex:
|
|
42
|
+
text = cls.LATEX_PATTERN.sub(" ", text)
|
|
43
|
+
if skip_arxiv_meta:
|
|
44
|
+
text = cls.ARXIV_META_PATTERN.sub(" ", text)
|
|
45
|
+
if skip_figures:
|
|
46
|
+
text = cls.FIGURE_CAPTION_PATTERN.sub(" ", text)
|
|
47
|
+
if skip_brackets:
|
|
48
|
+
text = cls.BRACKETS_PATTERN.sub(" ", text)
|
|
49
|
+
if skip_pdf_noise:
|
|
50
|
+
text = cls.SINGLE_LETTER_NOISE_PATTERN.sub(" ", text)
|
|
51
|
+
|
|
52
|
+
# Normalize spacing
|
|
53
|
+
text = re.sub(r"([.!?])([A-Za-z])", r"\1 \2", text)
|
|
54
|
+
text = re.sub(r"\s+", " ", text)
|
|
55
|
+
return text.strip()
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def normalize_spacing(text: str) -> str:
|
|
59
|
+
"""Ensure proper spacing after sentence-ending punctuation."""
|
|
60
|
+
return re.sub(r"([.!?])([A-Za-z])", r"\1 \2", text)
|
|
61
|
+
|
granuscore/utils.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from multiprocessing.pool import ThreadPool
|
|
3
|
+
from typing import Sequence, Iterator, TypeVar, Callable, Any
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
from spacy.util import is_package
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ensure_spacy_model(name: str = "en_core_web_sm") -> None:
|
|
13
|
+
if is_package(name):
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
from spacy.cli import download
|
|
17
|
+
download(name)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def iter_batches(items: Sequence[T], batch_size: int) -> Iterator[Sequence[T]]:
|
|
21
|
+
for i in range(0, len(items), batch_size):
|
|
22
|
+
yield items[i: i + batch_size]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_best_device():
|
|
26
|
+
if torch.cuda.is_available():
|
|
27
|
+
return torch.device("cuda")
|
|
28
|
+
if torch.backends.mps.is_available():
|
|
29
|
+
return torch.device("mps")
|
|
30
|
+
return torch.device("cpu")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def map_with_progress(
|
|
34
|
+
f: Callable,
|
|
35
|
+
xs: list[Any],
|
|
36
|
+
num_threads: int = os.cpu_count() or 10,
|
|
37
|
+
pbar: bool = False,
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Apply f to each element of xs, using a ThreadPool, and show progress.
|
|
41
|
+
"""
|
|
42
|
+
pbar_fn = tqdm if pbar else lambda x, *args, **kwargs: x
|
|
43
|
+
|
|
44
|
+
if os.getenv("debug"):
|
|
45
|
+
return list(map(f, pbar_fn(xs, total=len(xs))))
|
|
46
|
+
else:
|
|
47
|
+
with ThreadPool(min(num_threads, len(xs))) as pool:
|
|
48
|
+
return list(pbar_fn(pool.imap(f, xs), total=len(xs)))
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: granuscore
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Granularity scoring for natural language
|
|
5
|
+
Author-email: Lukas Ellinger <lukas.ellinger@tum.de>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/lukasellinger/granuscore
|
|
8
|
+
Project-URL: Issues, https://github.com/lukasellinger/granuscore/issues
|
|
9
|
+
Keywords: granularity,natural language processing,nlp,text analysis,semantic abstraction,specificity
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
18
|
+
Requires-Python: <3.13,>=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: faiss-cpu>=1.13.2
|
|
22
|
+
Requires-Dist: hierarchy_transformers>=0.1.1
|
|
23
|
+
Requires-Dist: lightgbm>=4.6.0
|
|
24
|
+
Requires-Dist: spacy>=3.8.11
|
|
25
|
+
Requires-Dist: torch>=2.9.1
|
|
26
|
+
Requires-Dist: numpy>=1.26.4
|
|
27
|
+
Requires-Dist: platformdirs>=3.10.0
|
|
28
|
+
Requires-Dist: requests>=2.31.0
|
|
29
|
+
Requires-Dist: tqdm>=4.66.0
|
|
30
|
+
Requires-Dist: openai>=2.21.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: optuna>=4.6.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pygam>=0.12.0; extra == "dev"
|
|
34
|
+
Requires-Dist: python-dotenv>=1.2.1; extra == "dev"
|
|
35
|
+
Requires-Dist: ollama>=0.6.1; extra == "dev"
|
|
36
|
+
Requires-Dist: google-genai>=1.66.0; extra == "dev"
|
|
37
|
+
Requires-Dist: wget>=3.2; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
[](https://pypi.org/project/granuscore/)
|
|
41
|
+
[](https://github.com/lukasellinger/granuscore)
|
|
42
|
+
# Granuscore
|
|
43
|
+
|
|
44
|
+
**Granuscore** is a Python library for measuring the *semantic granularity* of natural language text.
|
|
45
|
+
|
|
46
|
+
It provides an end-to-end pipeline that:
|
|
47
|
+
|
|
48
|
+
1. splits text into referential units,
|
|
49
|
+
2. assigns continuous granularity scores to each unit,
|
|
50
|
+
3. aggregates these scores into document-level estimates.
|
|
51
|
+
|
|
52
|
+
Granuscore is designed for analyzing how *fine-grained* or *coarse-grained* textual expressions are in applications such as question answering, educational dialogue, summarization, and scientific writing.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
Install from PyPI:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install granuscore
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or install the latest development version locally:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
git clone https://github.com/lukasellinger/granuscore.git
|
|
68
|
+
cd granuscore
|
|
69
|
+
pip install -e .
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Optional development dependencies:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install -e ".[dev]"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Quick Start
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from granuscore import GranuScore
|
|
84
|
+
|
|
85
|
+
scorer = GranuScore()
|
|
86
|
+
|
|
87
|
+
text = """
|
|
88
|
+
Tony Hawk was born in San Diego.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
score = scorer(text)
|
|
92
|
+
|
|
93
|
+
print(score)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
By default, Granuscore returns percentile scores, where higher values correspond to coarser-grained expressions.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Default Configuration
|
|
101
|
+
|
|
102
|
+
The default configuration reproduces the setup used in the paper.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
scorer = GranuScore()
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Equivalent to:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
scorer = GranuScore(
|
|
112
|
+
predictor_type="hit",
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Default settings:
|
|
117
|
+
|
|
118
|
+
- `predictor_type="hit"`
|
|
119
|
+
- `model_name="Hierarchy-Transformers/HiT-MiniLM-L12-WordNetNoun"`
|
|
120
|
+
- `search_method="random_anchors"`
|
|
121
|
+
- `random_anchors_k=999`
|
|
122
|
+
|
|
123
|
+
Required artifacts such as:
|
|
124
|
+
- FAISS indices,
|
|
125
|
+
- anchor vectors,
|
|
126
|
+
- LightGBM models,
|
|
127
|
+
- and reference percentile distributions
|
|
128
|
+
|
|
129
|
+
are automatically downloaded and cached on first use.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Important Compatibility Note
|
|
134
|
+
|
|
135
|
+
The default configuration works out of the box and is the recommended setup.
|
|
136
|
+
|
|
137
|
+
If you customize components such as:
|
|
138
|
+
- the embedding model,
|
|
139
|
+
- search method,
|
|
140
|
+
- FAISS index,
|
|
141
|
+
- anchor vectors,
|
|
142
|
+
- or LightGBM model,
|
|
143
|
+
|
|
144
|
+
you must ensure that all resources are compatible with each other.
|
|
145
|
+
|
|
146
|
+
For example, a LightGBM model trained using:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
search_method="random_anchors"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
should not be combined with:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
search_method="nearest_neighbor"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Similarly, FAISS indices, anchor vectors, percentile reference distributions, and LightGBM models must originate from the same embedding space and training configuration.
|
|
159
|
+
|
|
160
|
+
Compatibility between custom resources is not validated automatically.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Notebook Tutorial
|
|
165
|
+
|
|
166
|
+
An interactive introduction is available in:
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
notebooks/getting_started.ipynb
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Repository Structure
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
granuscore/
|
|
178
|
+
├── src/
|
|
179
|
+
│ └── granuscore/
|
|
180
|
+
│ ├── pipeline.py
|
|
181
|
+
│ ├── granularity_predictor.py
|
|
182
|
+
│ ├── claim_splitter.py
|
|
183
|
+
│ ├── bucket_output.py
|
|
184
|
+
│ ├── cache.py
|
|
185
|
+
│ └── artifacts.py
|
|
186
|
+
├── notebooks/
|
|
187
|
+
│ └── getting_started.ipynb
|
|
188
|
+
├── training_scripts/
|
|
189
|
+
├── evaluation/
|
|
190
|
+
├── assets/
|
|
191
|
+
├── data/ (needs to be externally downloaded)
|
|
192
|
+
├── pyproject.toml
|
|
193
|
+
├── LICENSE
|
|
194
|
+
└── README.md
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Reproducing Paper Experiments
|
|
200
|
+
|
|
201
|
+
The datasets and precomputed resources required to reproduce the experiments from the paper are available here:
|
|
202
|
+
|
|
203
|
+
https://drive.google.com/drive/folders/1mJdUENOxHEiuYn-_f1KRQ1PZggXJDnb4?usp=sharing
|
|
204
|
+
|
|
205
|
+
Download the archive and extract it into the repository root:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
unzip data.zip
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
This will create the expected directory structure used by the training and evaluation scripts.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Training Pipeline
|
|
216
|
+
|
|
217
|
+
Training uses precomputed `.pkl` feature files.
|
|
218
|
+
|
|
219
|
+
1. Generate precomputed datasets:
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
training_scripts/build_precalc_data/
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
2. Train LightGBM models:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
python training_scripts/train_lgb_models.py
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Citation
|
|
234
|
+
Updated Citation information will be added after publication.
|
|
235
|
+
|
|
236
|
+
```bibtex
|
|
237
|
+
@misc{ellinger2026granuscore,
|
|
238
|
+
title={Granuscore: A Reference-Free Measure of Granularity for Text Analysis and Question Answering},
|
|
239
|
+
author={Ellinger, Lukas and Fichtl, Alexander M. and Anschütz, Miriam and Groh, Georg},
|
|
240
|
+
year={2026}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
granuscore/__init__.py,sha256=nG10Jgicc_EngbTgxXyyNLL3M3GF6NOWMNmUYytKVdU,268
|
|
2
|
+
granuscore/artifcats.py,sha256=BgCatJuAgkERcfV6f_xS0NqyjsawOlhHrsrrm1Ce7vA,3973
|
|
3
|
+
granuscore/bucket_output.py,sha256=y9yiHOvhEXW-KwWtRIcdeal7uBj30rkLZIemYUZ8Sas,1633
|
|
4
|
+
granuscore/cache.py,sha256=HSPMMPDO0Dyi3dYwjXnuZ1P5szJeOpuaMXB6M15-2-c,6873
|
|
5
|
+
granuscore/claim_splitter.py,sha256=nub3fZj7E23Y4QvWrQcIC0hETO1ZlaaBVT-R7O9P5TQ,9785
|
|
6
|
+
granuscore/default_assets.py,sha256=A4zGxQ1AqheT3ja8naDR1f_RTaPfD93pOG82sRMfiNA,1287
|
|
7
|
+
granuscore/granularity_predictor.py,sha256=kzGGMHimedCTfHzVL4kz0lnDFvc7ZZvAqXm7_orWsro,25609
|
|
8
|
+
granuscore/loader.py,sha256=5I9Fd9phOszD-od7kVN7RiMycJXRIrFaGdyxmVs59jo,3059
|
|
9
|
+
granuscore/pipeline.py,sha256=apcFHLR2YGfZiXXThSsZqCeNNMDYd7rOADhmQb5-FTU,24396
|
|
10
|
+
granuscore/text_cleaner.py,sha256=nNfy2XUANgKhReM-occwut4LtxXfpe0s7fp-TGBZLwA,2109
|
|
11
|
+
granuscore/utils.py,sha256=h-yNEYA1zqazd0kAjjtERojH3vXHVZzAu8Jxh7B5rDM,1237
|
|
12
|
+
granuscore-1.0.0.dist-info/licenses/LICENSE,sha256=Sev5Z9X5Afo9ThLkUqBEevqX8ERAoFEn6rvEtQjtPwU,1075
|
|
13
|
+
granuscore-1.0.0.dist-info/METADATA,sha256=1cp9QnCHpwqLiStf41bpoOhl6hfegRtEfo7whDza_Mc,5882
|
|
14
|
+
granuscore-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
15
|
+
granuscore-1.0.0.dist-info/top_level.txt,sha256=BWgkEbJfbqBpAJdIrF2uXwelo7UuMlJkvyQc9xA0glg,11
|
|
16
|
+
granuscore-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Granuscore Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
granuscore
|