taxo 1.0.0__tar.gz
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.
- taxo-1.0.0/.gitignore +27 -0
- taxo-1.0.0/CITATION.cff +17 -0
- taxo-1.0.0/LICENSE +21 -0
- taxo-1.0.0/PKG-INFO +177 -0
- taxo-1.0.0/README.md +145 -0
- taxo-1.0.0/pyproject.toml +59 -0
- taxo-1.0.0/requirements.txt +9 -0
- taxo-1.0.0/src/taxo/__init__.py +533 -0
- taxo-1.0.0/usage.ipynb +281 -0
taxo-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
.eggs/
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
.venv/
|
|
10
|
+
|
|
11
|
+
# uv
|
|
12
|
+
uv.lock
|
|
13
|
+
|
|
14
|
+
# Packaging
|
|
15
|
+
taxotagger.egg-info/
|
|
16
|
+
|
|
17
|
+
# Test / coverage
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
# Editors / OS
|
|
23
|
+
.vscode/
|
|
24
|
+
.idea/
|
|
25
|
+
.DS_Store
|
|
26
|
+
|
|
27
|
+
|
taxo-1.0.0/CITATION.cff
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# This CITATION.cff file was generated with cffinit.
|
|
2
|
+
# Visit https://bit.ly/cffinit to generate yours today!
|
|
3
|
+
|
|
4
|
+
cff-version: 1.2.0
|
|
5
|
+
title: taxo
|
|
6
|
+
message: >-
|
|
7
|
+
If you use this software, please cite it using the
|
|
8
|
+
metadata from this file.
|
|
9
|
+
type: software
|
|
10
|
+
authors:
|
|
11
|
+
- given-names: Andreas
|
|
12
|
+
family-names: Hamm
|
|
13
|
+
email: andreas.hamm@dlr.de
|
|
14
|
+
affiliation: German Aerospace Center (DLR)
|
|
15
|
+
orcid: 'https://orcid.org/0000-0001-5854-851X'
|
|
16
|
+
url: 'https://elib.dlr.de/190966/'
|
|
17
|
+
license: MIT
|
taxo-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 German Aerospace Center (DLR)
|
|
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.
|
taxo-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: taxo
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Tag documents with taxonomy terms using embeddings and a concept hierarchy
|
|
5
|
+
Project-URL: Homepage, https://github.com/DLR-SC/taxo-tag
|
|
6
|
+
Project-URL: Repository, https://github.com/DLR-SC/taxo-tag
|
|
7
|
+
Author-email: Andreas Hamm <andreas.hamm@dlr.de>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: document-tagging,embeddings,hierarchy,sentence-transformers,taxonomy
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: networkx>=3.4.2
|
|
19
|
+
Requires-Dist: nltk>=3.9.1
|
|
20
|
+
Requires-Dist: numpy>=2.2.5
|
|
21
|
+
Requires-Dist: openpyxl>=3.1.5
|
|
22
|
+
Requires-Dist: pandas>=2.2.3
|
|
23
|
+
Requires-Dist: scipy>=1.14.1
|
|
24
|
+
Requires-Dist: sentence-transformers>=4.1.0
|
|
25
|
+
Requires-Dist: torch>=2.6.0
|
|
26
|
+
Requires-Dist: transformers>=4.46.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
30
|
+
Requires-Dist: twine; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# taxo
|
|
34
|
+
|
|
35
|
+
Tag documents with terms from a concept hierarchy (taxonomy) using sentence
|
|
36
|
+
embeddings.
|
|
37
|
+
|
|
38
|
+
`taxo` reads in a hierarchy of concepts, embeds their descriptions in a shared
|
|
39
|
+
vector space, and then finds the concepts most related to a document. It can
|
|
40
|
+
also aggregate sentence-level evidence along hierarchical paths to produce a
|
|
41
|
+
consistent set of top-level tags.
|
|
42
|
+
|
|
43
|
+
For the main ideas used in `taxo`, see
|
|
44
|
+
```
|
|
45
|
+
@inproceedings{dlr190966,
|
|
46
|
+
title = {Strategy Comparison for Semantic Zero-Shot Taxonomy Filters},
|
|
47
|
+
month = {October},
|
|
48
|
+
booktitle = {4th International Open Search Symposium (OSSYM 2022)},
|
|
49
|
+
author = {Hamm, Andreas},
|
|
50
|
+
year = {2022},
|
|
51
|
+
url = {https://elib.dlr.de/190966/}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
On Python >= 3.11:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install taxo
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This is a uv-style package, so `uv` works too:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uv add taxo
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
The package is both installed and imported as `taxo`.
|
|
71
|
+
|
|
72
|
+
Given a taxonomy (by this we mean a hierarchy of concepts, ordered in the structure of a directed acyclic graph (DAG)) - presented in tabular form as described below - and the text of a document, this package can be used to tag the document with the best-fitting taxonomy concepts.
|
|
73
|
+
|
|
74
|
+
The table used to specify the taxonomy is expected to be a Pandas dataframe. Thus, it can be imported from several tabular sources like csv files or Excel sheets. An example is the file `media.xlsx` included in this repository. It is based on the [IPTC media topics taxonomy](https://iptc.org/standards/media-topics/), a taxonomy frequently used for subject classification of news articles.
|
|
75
|
+
|
|
76
|
+
The following short excerpt illustrates the structure:
|
|
77
|
+
|
|
78
|
+
| key | label_en | description_en | label_de | description_de | parents |
|
|
79
|
+
|-----------|-------------------------|---------------------------------------------------------------------------------------------------------------------------------|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
|
|
80
|
+
| M00000000 | [TOP] | | [TOP] | | [] |
|
|
81
|
+
| M05000000 | education | All aspects of furthering knowledge, formally or informally | Erziehung, Ausbildung | Alle Aspekte der Förderung von Wissen für den Menschen von der Geburt bis zum Tod. | ['M00000000'] |
|
|
82
|
+
| M06000000 | environment | All aspects of protection, damage, and condition of the ecosystem of the planet earth and its surroundings. | Umwelt | Alle Aspekte des Schutzes, der Zerstörung und die Bedingungen des Ökosystems der Erde und ihrer Umgebung | ['M00000000'] |
|
|
83
|
+
| M07000000 | health | All aspects pertaining to the physical and mental welfare of living beings | Medizin, Gesundheit | Alle Aspekte des körperlichen und geistigen Wohlbefindens von Menschen. | ['M00000000'] |
|
|
84
|
+
| M20000400 | school | A building or institution in which education is provided | Schule | Gebäude oder Einrichtung, wo verschiedenartige Ausbildung angeboten wird. | ['M05000000'] |
|
|
85
|
+
| M20000405 | college and university | Institutions of higher learning that provide degrees in a course of study at the undergraduate, postgraduate or doctorate level | Hochschule | Ausbildung oberhalb des zweiten Levels, meist erwartet von Fachhochschulen und Universitäten, die in einer professionellen Qualifikation oder einem akademischen Grad führt | ['M20000400'] |
|
|
86
|
+
| M20000406 | college | Institution of higher learning, its specific scope is set differently in different countries | Fachhochschule | Institution der Lehre auf höherer Ebene, spezielle Form variiert von Land zu Land | ['M20000405'] |
|
|
87
|
+
| M20000407 | university | Institutions of higher learning capable of providing doctorate degrees | Universität | Institution der Lehre auf höherer Ebene, geeignet um Doktoren- und Professorentitel etc. zu verleihen. | ['M20000405'] |
|
|
88
|
+
| M20000446 | diseases and conditions | Any harmful or destructive health condition affecting humans. | Krankheit | Jeder gesundheitsgefährdende oder zerstörerische Zustand | ['M07000000'] |
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
The table has one row for every node (concept) of the taxonomy. Formally, there is one special node needed, the so called root or top node, which is the ancestor of all other nodes in the hierarchy. (See the node with key `M00000000` in the media example.)
|
|
92
|
+
|
|
93
|
+
Each row of the table has to specify:
|
|
94
|
+
- the concept's unique `key`
|
|
95
|
+
- language dependent labels for the concept
|
|
96
|
+
- short language dependent descriptions of the concept
|
|
97
|
+
- the keys of the hierarchical `parents` of the concept
|
|
98
|
+
|
|
99
|
+
The parents' keys have to be given as a string that evaluates to a list in Python (e.g., `"['concept1', 'concept2']"`). In the case of a hierarchy that is a tree, every concept has of course only one parent.
|
|
100
|
+
|
|
101
|
+
Given a 2-letter ISO code `xy` for a language, the row headers for labels and descriptions are `label_xy` and `description_xy`, respectively. The table is expected to have English entries (`en`) as default, and more entries for the other languages needed. The code in this package is also meant to handle German (`de`) in addition to English; other languages need small obvious modifications.
|
|
102
|
+
|
|
103
|
+
Tagging is based on semantic similarity considerations as calculated by a Sentence Transformer model. Any model provided by the sentence_transformers package can be used. The small model `all-MiniLM-L6-v2` works well for English (but not so well for other languages).
|
|
104
|
+
|
|
105
|
+
The code fragment below shows the principles of usage, finally executing the tagging by calling the method `identify_concepts` which has several tuning parameters which can be adapted to specific constellations and use cases.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import networkx as nx
|
|
109
|
+
import pandas as pd
|
|
110
|
+
from sentence_transformers import SentenceTransformer
|
|
111
|
+
|
|
112
|
+
import taxo
|
|
113
|
+
|
|
114
|
+
# Build a taxonomy from a dataframe (one row per concept).
|
|
115
|
+
df = pd.read_excel("media.xlsx")
|
|
116
|
+
df.fillna("", inplace=True)
|
|
117
|
+
|
|
118
|
+
tax = taxo.ConceptTaxonomy(
|
|
119
|
+
nx.DiGraph(),
|
|
120
|
+
root="M00000000", # key of the formal top concept
|
|
121
|
+
transformer="all-MiniLM-L6-v2",
|
|
122
|
+
default_lang="en",
|
|
123
|
+
)
|
|
124
|
+
tax.from_df(df)
|
|
125
|
+
|
|
126
|
+
# Tag a document.
|
|
127
|
+
doc = taxo.TaxedDocument(tax, text)
|
|
128
|
+
result = doc.identify_concepts(
|
|
129
|
+
top_k=10, # concepts kept when checking similarity to text
|
|
130
|
+
min_similarity=0.25, # minimum sentence-level similarity to be kept
|
|
131
|
+
title_weight=2, # weight of the first sentence (the document title)
|
|
132
|
+
saturation=1.5, # saturation parameter for score aggregation
|
|
133
|
+
min_agg=0.25, # minimum aggregated score for a concept to be returned
|
|
134
|
+
top_percent=80, # percentage kept when truncating the hierarchy on the top level
|
|
135
|
+
second_percent=80, # percentage kept when truncating the hierarchy on the second level
|
|
136
|
+
max_baseconcepts=7, # maximum number of returned (leaf) concepts
|
|
137
|
+
max_paths=2, # number of paths to the root considered per concept
|
|
138
|
+
with_scores=True, # include aggregated scores in the results
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Each entry of `result` is a hierarchical path from the root to a concept, as a
|
|
143
|
+
list of `(name, key)` pairs (optionally with an aggregated score appended).
|
|
144
|
+
|
|
145
|
+
See also the included Jupyter notebook `usage.ipynb`.
|
|
146
|
+
|
|
147
|
+
## API
|
|
148
|
+
|
|
149
|
+
### `ConceptTaxonomy`
|
|
150
|
+
|
|
151
|
+
Holds the concept hierarchy and its embeddings.
|
|
152
|
+
|
|
153
|
+
- `__init__(taxgraph, root, long_descr=True, transformer=None, default_lang="en", classifier=None, token=None)`
|
|
154
|
+
- `from_df(df_concepts, rename_columns=None)` — add/update concepts from a dataframe
|
|
155
|
+
- `to_df()` — export the hierarchy to a dataframe
|
|
156
|
+
- `describe_concepts(lang=None, intro=None, long=True)` — prepare comparison sentences
|
|
157
|
+
- `embed_concepts(model=None)` — compute the concept embeddings
|
|
158
|
+
- `get_concepts_in_text(text, top_k=10, threshold=0.3, sim_func=None)` — similarity or zero-shot search
|
|
159
|
+
- `get_parent_concepts(child, top_parent=None, lang=None)` — paths from a concept to the root
|
|
160
|
+
- `show_concepts(concepts, lang=None, scores=None)` — format results for output
|
|
161
|
+
|
|
162
|
+
### `TaxedDocument`
|
|
163
|
+
|
|
164
|
+
Prepares a document for concept identification.
|
|
165
|
+
|
|
166
|
+
- `set_parameters(top_k=10, threshold=0.3, weight_dev=None, kparm=1.0)`
|
|
167
|
+
- `score_concepts_overall()` / `score_concepts_sentencewise()`
|
|
168
|
+
- `aggregate_sentence_scores(min_agg_score=0)`
|
|
169
|
+
- `find_best_paths(concept, n)`
|
|
170
|
+
- `evaluate_hierarchy(max_paths=2)` / `restrict_hierarchy(level, percentage)`
|
|
171
|
+
- `get_relevant_concepts(min_score=None, max_number=100)`
|
|
172
|
+
- `identify_concepts(...)` — all of the above in one call
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT — DLR. See LICENSE file.
|
taxo-1.0.0/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# taxo
|
|
2
|
+
|
|
3
|
+
Tag documents with terms from a concept hierarchy (taxonomy) using sentence
|
|
4
|
+
embeddings.
|
|
5
|
+
|
|
6
|
+
`taxo` reads in a hierarchy of concepts, embeds their descriptions in a shared
|
|
7
|
+
vector space, and then finds the concepts most related to a document. It can
|
|
8
|
+
also aggregate sentence-level evidence along hierarchical paths to produce a
|
|
9
|
+
consistent set of top-level tags.
|
|
10
|
+
|
|
11
|
+
For the main ideas used in `taxo`, see
|
|
12
|
+
```
|
|
13
|
+
@inproceedings{dlr190966,
|
|
14
|
+
title = {Strategy Comparison for Semantic Zero-Shot Taxonomy Filters},
|
|
15
|
+
month = {October},
|
|
16
|
+
booktitle = {4th International Open Search Symposium (OSSYM 2022)},
|
|
17
|
+
author = {Hamm, Andreas},
|
|
18
|
+
year = {2022},
|
|
19
|
+
url = {https://elib.dlr.de/190966/}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
On Python >= 3.11:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install taxo
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This is a uv-style package, so `uv` works too:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv add taxo
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
The package is both installed and imported as `taxo`.
|
|
39
|
+
|
|
40
|
+
Given a taxonomy (by this we mean a hierarchy of concepts, ordered in the structure of a directed acyclic graph (DAG)) - presented in tabular form as described below - and the text of a document, this package can be used to tag the document with the best-fitting taxonomy concepts.
|
|
41
|
+
|
|
42
|
+
The table used to specify the taxonomy is expected to be a Pandas dataframe. Thus, it can be imported from several tabular sources like csv files or Excel sheets. An example is the file `media.xlsx` included in this repository. It is based on the [IPTC media topics taxonomy](https://iptc.org/standards/media-topics/), a taxonomy frequently used for subject classification of news articles.
|
|
43
|
+
|
|
44
|
+
The following short excerpt illustrates the structure:
|
|
45
|
+
|
|
46
|
+
| key | label_en | description_en | label_de | description_de | parents |
|
|
47
|
+
|-----------|-------------------------|---------------------------------------------------------------------------------------------------------------------------------|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
|
|
48
|
+
| M00000000 | [TOP] | | [TOP] | | [] |
|
|
49
|
+
| M05000000 | education | All aspects of furthering knowledge, formally or informally | Erziehung, Ausbildung | Alle Aspekte der Förderung von Wissen für den Menschen von der Geburt bis zum Tod. | ['M00000000'] |
|
|
50
|
+
| M06000000 | environment | All aspects of protection, damage, and condition of the ecosystem of the planet earth and its surroundings. | Umwelt | Alle Aspekte des Schutzes, der Zerstörung und die Bedingungen des Ökosystems der Erde und ihrer Umgebung | ['M00000000'] |
|
|
51
|
+
| M07000000 | health | All aspects pertaining to the physical and mental welfare of living beings | Medizin, Gesundheit | Alle Aspekte des körperlichen und geistigen Wohlbefindens von Menschen. | ['M00000000'] |
|
|
52
|
+
| M20000400 | school | A building or institution in which education is provided | Schule | Gebäude oder Einrichtung, wo verschiedenartige Ausbildung angeboten wird. | ['M05000000'] |
|
|
53
|
+
| M20000405 | college and university | Institutions of higher learning that provide degrees in a course of study at the undergraduate, postgraduate or doctorate level | Hochschule | Ausbildung oberhalb des zweiten Levels, meist erwartet von Fachhochschulen und Universitäten, die in einer professionellen Qualifikation oder einem akademischen Grad führt | ['M20000400'] |
|
|
54
|
+
| M20000406 | college | Institution of higher learning, its specific scope is set differently in different countries | Fachhochschule | Institution der Lehre auf höherer Ebene, spezielle Form variiert von Land zu Land | ['M20000405'] |
|
|
55
|
+
| M20000407 | university | Institutions of higher learning capable of providing doctorate degrees | Universität | Institution der Lehre auf höherer Ebene, geeignet um Doktoren- und Professorentitel etc. zu verleihen. | ['M20000405'] |
|
|
56
|
+
| M20000446 | diseases and conditions | Any harmful or destructive health condition affecting humans. | Krankheit | Jeder gesundheitsgefährdende oder zerstörerische Zustand | ['M07000000'] |
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
The table has one row for every node (concept) of the taxonomy. Formally, there is one special node needed, the so called root or top node, which is the ancestor of all other nodes in the hierarchy. (See the node with key `M00000000` in the media example.)
|
|
60
|
+
|
|
61
|
+
Each row of the table has to specify:
|
|
62
|
+
- the concept's unique `key`
|
|
63
|
+
- language dependent labels for the concept
|
|
64
|
+
- short language dependent descriptions of the concept
|
|
65
|
+
- the keys of the hierarchical `parents` of the concept
|
|
66
|
+
|
|
67
|
+
The parents' keys have to be given as a string that evaluates to a list in Python (e.g., `"['concept1', 'concept2']"`). In the case of a hierarchy that is a tree, every concept has of course only one parent.
|
|
68
|
+
|
|
69
|
+
Given a 2-letter ISO code `xy` for a language, the row headers for labels and descriptions are `label_xy` and `description_xy`, respectively. The table is expected to have English entries (`en`) as default, and more entries for the other languages needed. The code in this package is also meant to handle German (`de`) in addition to English; other languages need small obvious modifications.
|
|
70
|
+
|
|
71
|
+
Tagging is based on semantic similarity considerations as calculated by a Sentence Transformer model. Any model provided by the sentence_transformers package can be used. The small model `all-MiniLM-L6-v2` works well for English (but not so well for other languages).
|
|
72
|
+
|
|
73
|
+
The code fragment below shows the principles of usage, finally executing the tagging by calling the method `identify_concepts` which has several tuning parameters which can be adapted to specific constellations and use cases.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import networkx as nx
|
|
77
|
+
import pandas as pd
|
|
78
|
+
from sentence_transformers import SentenceTransformer
|
|
79
|
+
|
|
80
|
+
import taxo
|
|
81
|
+
|
|
82
|
+
# Build a taxonomy from a dataframe (one row per concept).
|
|
83
|
+
df = pd.read_excel("media.xlsx")
|
|
84
|
+
df.fillna("", inplace=True)
|
|
85
|
+
|
|
86
|
+
tax = taxo.ConceptTaxonomy(
|
|
87
|
+
nx.DiGraph(),
|
|
88
|
+
root="M00000000", # key of the formal top concept
|
|
89
|
+
transformer="all-MiniLM-L6-v2",
|
|
90
|
+
default_lang="en",
|
|
91
|
+
)
|
|
92
|
+
tax.from_df(df)
|
|
93
|
+
|
|
94
|
+
# Tag a document.
|
|
95
|
+
doc = taxo.TaxedDocument(tax, text)
|
|
96
|
+
result = doc.identify_concepts(
|
|
97
|
+
top_k=10, # concepts kept when checking similarity to text
|
|
98
|
+
min_similarity=0.25, # minimum sentence-level similarity to be kept
|
|
99
|
+
title_weight=2, # weight of the first sentence (the document title)
|
|
100
|
+
saturation=1.5, # saturation parameter for score aggregation
|
|
101
|
+
min_agg=0.25, # minimum aggregated score for a concept to be returned
|
|
102
|
+
top_percent=80, # percentage kept when truncating the hierarchy on the top level
|
|
103
|
+
second_percent=80, # percentage kept when truncating the hierarchy on the second level
|
|
104
|
+
max_baseconcepts=7, # maximum number of returned (leaf) concepts
|
|
105
|
+
max_paths=2, # number of paths to the root considered per concept
|
|
106
|
+
with_scores=True, # include aggregated scores in the results
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Each entry of `result` is a hierarchical path from the root to a concept, as a
|
|
111
|
+
list of `(name, key)` pairs (optionally with an aggregated score appended).
|
|
112
|
+
|
|
113
|
+
See also the included Jupyter notebook `usage.ipynb`.
|
|
114
|
+
|
|
115
|
+
## API
|
|
116
|
+
|
|
117
|
+
### `ConceptTaxonomy`
|
|
118
|
+
|
|
119
|
+
Holds the concept hierarchy and its embeddings.
|
|
120
|
+
|
|
121
|
+
- `__init__(taxgraph, root, long_descr=True, transformer=None, default_lang="en", classifier=None, token=None)`
|
|
122
|
+
- `from_df(df_concepts, rename_columns=None)` — add/update concepts from a dataframe
|
|
123
|
+
- `to_df()` — export the hierarchy to a dataframe
|
|
124
|
+
- `describe_concepts(lang=None, intro=None, long=True)` — prepare comparison sentences
|
|
125
|
+
- `embed_concepts(model=None)` — compute the concept embeddings
|
|
126
|
+
- `get_concepts_in_text(text, top_k=10, threshold=0.3, sim_func=None)` — similarity or zero-shot search
|
|
127
|
+
- `get_parent_concepts(child, top_parent=None, lang=None)` — paths from a concept to the root
|
|
128
|
+
- `show_concepts(concepts, lang=None, scores=None)` — format results for output
|
|
129
|
+
|
|
130
|
+
### `TaxedDocument`
|
|
131
|
+
|
|
132
|
+
Prepares a document for concept identification.
|
|
133
|
+
|
|
134
|
+
- `set_parameters(top_k=10, threshold=0.3, weight_dev=None, kparm=1.0)`
|
|
135
|
+
- `score_concepts_overall()` / `score_concepts_sentencewise()`
|
|
136
|
+
- `aggregate_sentence_scores(min_agg_score=0)`
|
|
137
|
+
- `find_best_paths(concept, n)`
|
|
138
|
+
- `evaluate_hierarchy(max_paths=2)` / `restrict_hierarchy(level, percentage)`
|
|
139
|
+
- `get_relevant_concepts(min_score=None, max_number=100)`
|
|
140
|
+
- `identify_concepts(...)` — all of the above in one call
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT — DLR. See LICENSE file.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "taxo"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Tag documents with taxonomy terms using embeddings and a concept hierarchy"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Andreas Hamm", email = "andreas.hamm@dlr.de" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["taxonomy", "document-tagging", "embeddings", "sentence-transformers", "hierarchy"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"networkx>=3.4.2",
|
|
26
|
+
"nltk>=3.9.1",
|
|
27
|
+
"numpy>=2.2.5",
|
|
28
|
+
"pandas>=2.2.3",
|
|
29
|
+
"sentence_transformers>=4.1.0",
|
|
30
|
+
"scipy>=1.14.1",
|
|
31
|
+
"openpyxl>=3.1.5",
|
|
32
|
+
"torch>=2.6.0",
|
|
33
|
+
"transformers>=4.46.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
"build",
|
|
39
|
+
"twine",
|
|
40
|
+
"pytest",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/DLR-SC/taxo-tag"
|
|
45
|
+
Repository = "https://github.com/DLR-SC/taxo-tag"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["src/taxo"]
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.sdist]
|
|
51
|
+
include = [
|
|
52
|
+
"src/",
|
|
53
|
+
"README.md",
|
|
54
|
+
"LICENSE",
|
|
55
|
+
"CITATION.cff",
|
|
56
|
+
"pyproject.toml",
|
|
57
|
+
"requirements.txt",
|
|
58
|
+
"usage.ipynb",
|
|
59
|
+
]
|
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
"""Python module for tagging documents with taxonomy terms
|
|
2
|
+
Author: Andreas Hamm (andreas.hamm@dlr.de)
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Copyright: DLR
|
|
5
|
+
License: MIT
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
|
|
11
|
+
import nltk
|
|
12
|
+
from nltk.tokenize import sent_tokenize
|
|
13
|
+
import pandas as pd
|
|
14
|
+
import numpy as np
|
|
15
|
+
import networkx as nx
|
|
16
|
+
from sentence_transformers import SentenceTransformer, util
|
|
17
|
+
from transformers import pipeline
|
|
18
|
+
import ast
|
|
19
|
+
|
|
20
|
+
# Maps two-letter ISO codes to the punkt_tab language directory names NLTK's
|
|
21
|
+
# Punkt tokenizer loads. These must match the directory names on disk
|
|
22
|
+
# (e.g. "german", "english"): punkt.py does a plain ``find("tokenizers/punkt_tab/{lang}/")``
|
|
23
|
+
# with no case normalization, so capitalization must match the filesystem
|
|
24
|
+
# (which is fine on Windows but would fail on case-sensitive Linux/macOS).
|
|
25
|
+
# Only models actually installed as ``punkt_tab`` will work.
|
|
26
|
+
_ISO_TO_NLTK_LANGUAGE = {
|
|
27
|
+
"en": "english",
|
|
28
|
+
"de": "german",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _ensure_punkt(language: str = "english") -> bool:
|
|
33
|
+
"""Ensures the punkt_tab tokenizer for ``language`` is available.
|
|
34
|
+
|
|
35
|
+
NLTK only downloads punkt_tab lazily and raises ``LookupError`` otherwise.
|
|
36
|
+
On first use it attempts a silent download of ``punkt_tab`` and retries.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
language (str): The punkt_tab language directory name, e.g. ``"english"`` or ``"german"``.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
bool: ``True`` if the tokenizer is available afterwards, ``False`` otherwise.
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
nltk.data.find(f"tokenizers/punkt_tab/{language}")
|
|
46
|
+
return True
|
|
47
|
+
except LookupError:
|
|
48
|
+
try:
|
|
49
|
+
return bool(nltk.download("punkt_tab", quiet=True))
|
|
50
|
+
except Exception:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
class ConceptTaxonomy:
|
|
54
|
+
"""Class for working with a concept hierarchy including concept embeddings
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
default_lang (str): Default language (two-letter ISO code of a supported language)
|
|
58
|
+
tg (networkx.DiGraph): Graph defining the concept hierarchy. The concepts are nodes with attributes
|
|
59
|
+
- ``key``
|
|
60
|
+
- ``label_{isolang}``
|
|
61
|
+
- ``description_{isolang}``
|
|
62
|
+
where ``isolang`` is the two-letter ISO code of each supported language (e.g., ``"en"``, ``"de"``).
|
|
63
|
+
The edges point from concept nodes to their parents.
|
|
64
|
+
root (str): Key of the formal root node.
|
|
65
|
+
transformer (sentence_transformers.SentenceTransformer): SentenceTransformer model used for initial embeddings (only set if ``transformer`` was passed).
|
|
66
|
+
classifier (None or transformers.pipeline): Optionally a Huggingface zero-shot-classification pipeline.
|
|
67
|
+
desc_lang (str): Current language of concept descriptions (two-letter ISO code of a supported language).
|
|
68
|
+
desc_concepts (List[Tuple[str, str, str]]): List of ``(key, label, sentence)`` triples for all concepts, with labels and
|
|
69
|
+
descriptive sentences in the current language.
|
|
70
|
+
model (sentence_transformers.SentenceTransformer): SentenceTransformer model used for current embeddings (only set if ``transformer`` was passed).
|
|
71
|
+
emb_concepts (Optional[numpy.ndarray]): Row-normalized embedding vectors for all concepts in ``desc_concepts``.
|
|
72
|
+
token (None or str): Optionally Huggingface Access token.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
taxgraph: nx.DiGraph,
|
|
78
|
+
root: str,
|
|
79
|
+
long_descr: bool = True,
|
|
80
|
+
transformer: Optional[Union[str, SentenceTransformer]] = None,
|
|
81
|
+
default_lang: str = "en",
|
|
82
|
+
classifier: Optional[str] = None,
|
|
83
|
+
token: Optional[str] = None,
|
|
84
|
+
):
|
|
85
|
+
"""Constructs a ``ConceptTaxonomy``, optionally computing an initial embedding.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
taxgraph (networkx.DiGraph): Graph defining the concept hierarchy. The concepts are nodes with attributes
|
|
89
|
+
- ``key``
|
|
90
|
+
- ``label_{isolang}``
|
|
91
|
+
- ``description_{isolang}``
|
|
92
|
+
where ``isolang`` is the two-letter ISO code of each supported language (e.g., ``"en"``, ``"de"``)
|
|
93
|
+
root (str): Key of the formal root node. Must be a node in ``taxgraph``.
|
|
94
|
+
long_descr (bool, optional): Whether to include the concept descriptions in the concept comparison sentences. Defaults to ``True``.
|
|
95
|
+
transformer (Union[str, SentenceTransformer], optional): SentenceTransformer model (or its name) used for initial embeddings. Defaults to ``None``.
|
|
96
|
+
default_lang (str, optional): Default language (two-letter ISO code of a supported language). Defaults to ``"en"``.
|
|
97
|
+
classifier (str, optional): Optionally a Huggingface zero-shot-classification model name. Defaults to ``None``.
|
|
98
|
+
token (str, optional): Huggingface access token. Defaults to ``None``.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
ValueError: If ``root`` is not a node in ``taxgraph``.
|
|
102
|
+
"""
|
|
103
|
+
self.default_lang = default_lang
|
|
104
|
+
self.tg = taxgraph
|
|
105
|
+
self.root = root
|
|
106
|
+
self.token = token
|
|
107
|
+
# Ensure the punkt_tab sentence tokenizer for the default language is
|
|
108
|
+
# available, since score_concepts_sentencewise depends on it.
|
|
109
|
+
_ensure_punkt(_ISO_TO_NLTK_LANGUAGE.get(self.default_lang, "english"))
|
|
110
|
+
if long_descr:
|
|
111
|
+
self.describe_concepts()
|
|
112
|
+
else:
|
|
113
|
+
self.describe_concepts(long=False)
|
|
114
|
+
if transformer:
|
|
115
|
+
self.transformer = transformer
|
|
116
|
+
self.embed_concepts(transformer)
|
|
117
|
+
if classifier:
|
|
118
|
+
self.classifier = pipeline("zero-shot-classification", model=classifier)
|
|
119
|
+
else:
|
|
120
|
+
self.classifier = None
|
|
121
|
+
|
|
122
|
+
def from_df(
|
|
123
|
+
self,
|
|
124
|
+
df_concepts: pd.DataFrame,
|
|
125
|
+
rename_columns: Optional[Dict[str, str]] = None,
|
|
126
|
+
) -> "ConceptTaxonomy":
|
|
127
|
+
"""Reads in new concepts from a dataframe.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
df_concepts (pandas.DataFrame): Dataframe. Each row contains a node to be added to the graph defining the concept hierarchy.
|
|
131
|
+
The dataframe has to have the columns
|
|
132
|
+
- ``key`` for the concept key
|
|
133
|
+
- ``parents``, a list with the parent keys
|
|
134
|
+
- ``label_xx`` with ``xx`` the default language for the concept label
|
|
135
|
+
- ``description_xx`` with ``xx`` the default language for the concept description (might be empty)
|
|
136
|
+
and optionally further ``label_yz``, ``description_yz`` for other supported languages, and also other attributes
|
|
137
|
+
rename_columns (dict, optional): Dictionary for optionally renaming column headings. Defaults to ``{}``.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
ConceptTaxonomy: This instance, to allow method chaining.
|
|
141
|
+
"""
|
|
142
|
+
if rename_columns is None:
|
|
143
|
+
rename_columns = {}
|
|
144
|
+
if rename_columns:
|
|
145
|
+
df_concepts = df_concepts.rename(columns=rename_columns)
|
|
146
|
+
if len(set(df_concepts.keys()) & set(["key", f"label_{self.default_lang}", f"description_{self.default_lang}", "parents"])) < 4:
|
|
147
|
+
self.describe_concepts()
|
|
148
|
+
if self.transformer:
|
|
149
|
+
self.embed_concepts()
|
|
150
|
+
return self
|
|
151
|
+
for index, row in df_concepts.iterrows():
|
|
152
|
+
self.tg.add_node(row["key"], **{k: v for k, v in row.items() if k not in ["key", "parents"]})
|
|
153
|
+
for index, row in df_concepts.iterrows():
|
|
154
|
+
for p in ast.literal_eval(row["parents"]):
|
|
155
|
+
self.tg.add_edge(row["key"], p)
|
|
156
|
+
# Refresh the derived concept/embedding caches, since the nodes above
|
|
157
|
+
# changed but the caches from __init__ did not.
|
|
158
|
+
self.describe_concepts()
|
|
159
|
+
if self.transformer:
|
|
160
|
+
self.embed_concepts()
|
|
161
|
+
return self
|
|
162
|
+
|
|
163
|
+
def to_df(self) -> pd.DataFrame:
|
|
164
|
+
"""Writes the concept hierarchy into a dataframe.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
pandas.DataFrame: Dataframe. Each row contains one concept. The columns are ``key``, all attributes of the concept nodes, and ``parents``.
|
|
168
|
+
"""
|
|
169
|
+
records = []
|
|
170
|
+
for conc, attr in self.tg.nodes.items():
|
|
171
|
+
records.append({
|
|
172
|
+
"key": conc,
|
|
173
|
+
**attr,
|
|
174
|
+
"parents": [p for p in self.tg.predecessors(conc)]
|
|
175
|
+
})
|
|
176
|
+
return pd.DataFrame(records)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def describe_concepts(
|
|
180
|
+
self,
|
|
181
|
+
lang: Optional[str] = None,
|
|
182
|
+
intro: Optional[str] = None,
|
|
183
|
+
long: bool = True,
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Fills the attributes :pyattr:`desc_concepts` (a list of ``(key, label, sentence)`` triples) and :pyattr:`desc_lang`.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
lang (str, optional): Two-letter ISO code for language to use for concept names and descriptions. Defaults to ``None``, which means ``self.default_lang``.
|
|
189
|
+
intro (str, optional): Phrase for forming concept comparison sentences. Defaults to ``'This text is about '`` (which gets translated into ``'Dieser Text handelt von '`` in German).
|
|
190
|
+
long (bool, optional): Whether to include the concept descriptions in the concept comparison sentences. Defaults to ``True``.
|
|
191
|
+
"""
|
|
192
|
+
if lang is None:
|
|
193
|
+
lang = self.default_lang
|
|
194
|
+
if intro is None:
|
|
195
|
+
if lang == "de":
|
|
196
|
+
intro = "Dieser Text handelt von "
|
|
197
|
+
elif lang == "en":
|
|
198
|
+
intro = "This text is about "
|
|
199
|
+
else:
|
|
200
|
+
intro = ""
|
|
201
|
+
self.desc_lang = lang
|
|
202
|
+
self.desc_concepts = []
|
|
203
|
+
for ckey, cattrib in self.tg.nodes.items():
|
|
204
|
+
label = cattrib.get(f"label_{lang}", cattrib.get("label_en"))
|
|
205
|
+
if label is None:
|
|
206
|
+
continue
|
|
207
|
+
concept_sent = intro + label
|
|
208
|
+
if long:
|
|
209
|
+
description = cattrib.get(f"description_{lang}", cattrib.get("description_en"))
|
|
210
|
+
if description:
|
|
211
|
+
concept_sent += " (" + description + ")"
|
|
212
|
+
self.desc_concepts.append((ckey, label, concept_sent))
|
|
213
|
+
|
|
214
|
+
def embed_concepts(self, model: Optional[SentenceTransformer] = None) -> None:
|
|
215
|
+
"""Fills the attribute :pyattr:`emb_concepts` (the embedding of all concept comparison sentences) and sets :pyattr:`model`.
|
|
216
|
+
On large taxonomies, this method will have a considerable execution time.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
model (sentence_transformers.SentenceTransformer, optional): SentenceTransformer model to be used for embeddings. Defaults to ``None``, which reuses ``self.transformer``.
|
|
220
|
+
"""
|
|
221
|
+
if model is None:
|
|
222
|
+
model = self.transformer
|
|
223
|
+
self.model = model
|
|
224
|
+
if len(self.desc_concepts) == 0:
|
|
225
|
+
self.emb_concepts = []
|
|
226
|
+
return
|
|
227
|
+
emb_sents = model.encode([x[2] for x in self.desc_concepts], convert_to_numpy=True)
|
|
228
|
+
self.emb_concepts = emb_sents / np.linalg.norm(emb_sents, axis=1)[:, None]
|
|
229
|
+
|
|
230
|
+
def get_concepts_in_text(
|
|
231
|
+
self,
|
|
232
|
+
text: str,
|
|
233
|
+
top_k: int = 10,
|
|
234
|
+
threshold: float = 0.3,
|
|
235
|
+
sim_func: Optional[Callable[[Any, Any], Any]] = None,
|
|
236
|
+
) -> List[Tuple[str, str, float]]:
|
|
237
|
+
"""Similarity search for taxonomy concepts in a given text.
|
|
238
|
+
Uses either a zero-shot classification transformers pipeline (given as optional class attribute ``classifier``) or, by default, cosine similarity in the SentenceTransformer model's embedding space.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
text (str): Text.
|
|
242
|
+
top_k (int, optional): Maximum number of concepts returned. Defaults to ``10``.
|
|
243
|
+
threshold (float, optional): Minimum similarity threshold. Defaults to ``0.3``.
|
|
244
|
+
sim_func (Callable[[Tensor, Tensor], Tensor], optional): Similarity function passed to ``util.semantic_search``. Defaults to ``None``, which means cosine similarity.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
list: List of found concepts as ``(concept key, concept name, similarity score)`` triples, sorted by descending score.
|
|
248
|
+
"""
|
|
249
|
+
if len(self.desc_concepts) == 0:
|
|
250
|
+
return []
|
|
251
|
+
if self.classifier:
|
|
252
|
+
premises = [cd[2] for cd in self.desc_concepts]
|
|
253
|
+
clion = self.classifier(text, premises, hypothesis_template="{}", multi_class=True)
|
|
254
|
+
scored = [(cd[0], cd[1], score)
|
|
255
|
+
for cd, score in zip(self.desc_concepts, clion["scores"])
|
|
256
|
+
if score > threshold]
|
|
257
|
+
return sorted(scored, key=lambda z: -z[2])[:top_k]
|
|
258
|
+
emb_sent = self.model.encode(text, convert_to_numpy=True)
|
|
259
|
+
if sim_func is not None:
|
|
260
|
+
return [(self.desc_concepts[x["corpus_id"]][0], self.desc_concepts[x["corpus_id"]][1], x["score"])
|
|
261
|
+
for x in util.semantic_search(emb_sent, self.emb_concepts, top_k=top_k, score_function=sim_func)[0] if x["score"] > threshold]
|
|
262
|
+
return [(self.desc_concepts[x["corpus_id"]][0], self.desc_concepts[x["corpus_id"]][1], x["score"])
|
|
263
|
+
for x in util.semantic_search(emb_sent, self.emb_concepts, top_k=top_k)[0] if x["score"] > threshold]
|
|
264
|
+
|
|
265
|
+
def get_parent_concepts(
|
|
266
|
+
self,
|
|
267
|
+
child: str,
|
|
268
|
+
top_parent: Optional[str] = None,
|
|
269
|
+
lang: Optional[str] = None,
|
|
270
|
+
) -> Iterator[List[Union[str, Tuple[str, str, str]]]]:
|
|
271
|
+
"""Returns paths of predecessors of a given child concept, up back to a ``top_parent``.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
child (str): Key of child concept.
|
|
275
|
+
top_parent (str, optional): Key of earliest ancestor. Defaults to ``None``, in which case it is the root node of the hierarchy.
|
|
276
|
+
lang (str, optional): Two-letter ISO code for language to use for concept names and descriptions. Defaults to ``None``, in which case just the concept keys are returned.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
list: An iterator of paths to ``top_parent``, each path being a list of concepts. If ``lang`` is ``None``, each path is a list of concept keys; otherwise each concept is a ``(key, name, description)`` triple.
|
|
280
|
+
"""
|
|
281
|
+
if top_parent is None:
|
|
282
|
+
top_parent = self.root
|
|
283
|
+
paths = nx.all_simple_paths(self.tg, target=top_parent, source=child)
|
|
284
|
+
if lang is None:
|
|
285
|
+
return paths
|
|
286
|
+
return [[(k, self.tg.nodes[k][f"label_{lang}"], self.tg.nodes[k][f"description_{lang}"]) for k in p] for p in paths]
|
|
287
|
+
|
|
288
|
+
def show_concepts(
|
|
289
|
+
self,
|
|
290
|
+
concepts: List[List[str]],
|
|
291
|
+
lang: Optional[str] = None,
|
|
292
|
+
scores: Optional[List[float]] = None,
|
|
293
|
+
) -> List[List[Union[Tuple[str, str], Tuple[str, str, float]]]]:
|
|
294
|
+
"""Convenience function for producing output of concept results.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
concepts (list): List of hierarchy paths leading to concepts, each path represented as a list of concept keys.
|
|
298
|
+
lang (str, optional): Two-letter ISO code for language to use for concept names and descriptions. Defaults to ``None``, which means ``self.default_lang``.
|
|
299
|
+
scores (list, optional): List of scores along a hierarchical concept path. Defaults to ``None``.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
list: List of lists of ``(concept name, concept key)`` pairs, or ``(concept name, concept key, aggregated score)`` triples when ``scores`` is given.
|
|
303
|
+
"""
|
|
304
|
+
if lang is None:
|
|
305
|
+
lang = self.default_lang
|
|
306
|
+
if scores is None:
|
|
307
|
+
return [
|
|
308
|
+
[(self.tg.nodes[ck].get(f"label_{lang}", ck), ck) for ck in path]
|
|
309
|
+
for path in concepts
|
|
310
|
+
]
|
|
311
|
+
return [
|
|
312
|
+
[(self.tg.nodes[ck].get(f"label_{lang}", ck), ck, sc) for ck in path]
|
|
313
|
+
for path, sc in zip(concepts, scores)
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class TaxedDocument:
|
|
318
|
+
"""Class for working with a document to detect related concepts of a taxonomy in it
|
|
319
|
+
|
|
320
|
+
Attributes:
|
|
321
|
+
taxonomy (ConceptTaxonomy): The taxonomy from which the concept tags to the document are taken
|
|
322
|
+
doc_text (str): Text of the document
|
|
323
|
+
overall_concepts (Dict[str, float]): Concept scores for whole text (Key: Concept key, Value: Score)
|
|
324
|
+
sentences_concepts (Dict[str, List[float]]): Sentencewise concept scores (Key: Concept key, Value: List of sentence scores)
|
|
325
|
+
aggregated_concepts (Dict[str, float]): Aggregated concept scores (Key: Concept key, Value: Score)
|
|
326
|
+
hierarchy_distribution (Dict[int, Dict[str, float]]): Aggregated concept scores summed up along hierarchical upward paths.
|
|
327
|
+
Key: Hierarchy level, Value: Dict with Key: Concept key, Value: Summed-up score divided by path length
|
|
328
|
+
hierarchy_restriction (Dict[int, List[str]]): Consistency-restricted concepts on hierarchical levels.
|
|
329
|
+
Key: Hierarchical level, Value: Restricted list of concepts that make up the required percentage of scores
|
|
330
|
+
top_k (int): Current number of concepts retained when checking similarity to text
|
|
331
|
+
threshold (float): Current minimum similarity for considering concepts
|
|
332
|
+
weight_dev (Dict[int, float]): Dictionary for setting sentence weights different from 1 (Key: sentence number, Value: Sentence weight)
|
|
333
|
+
kparm (float): Current saturation parameter for aggregating similarities
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
def __init__(self, taxonomy: ConceptTaxonomy, doc_text: str):
|
|
337
|
+
"""Constructor for TaxedDocument.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
taxonomy (ConceptTaxonomy): The taxonomy to use.
|
|
341
|
+
doc_text (str): Text of the document.
|
|
342
|
+
"""
|
|
343
|
+
self.taxonomy = taxonomy
|
|
344
|
+
self.doc_text = doc_text
|
|
345
|
+
self.overall_concepts = None
|
|
346
|
+
self.sentences_concepts = None
|
|
347
|
+
self.aggregated_concepts = None
|
|
348
|
+
self.hierarchy_distribution = None
|
|
349
|
+
self.hierarchy_restriction = {}
|
|
350
|
+
self.set_parameters()
|
|
351
|
+
|
|
352
|
+
def set_parameters(
|
|
353
|
+
self,
|
|
354
|
+
top_k: int = 10,
|
|
355
|
+
threshold: float = 0.3,
|
|
356
|
+
weight_dev: Optional[Dict[int, float]] = None,
|
|
357
|
+
kparm: float = 1.0,
|
|
358
|
+
) -> None:
|
|
359
|
+
"""Set the parameters for taxonomy concept assignment to document text.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
top_k (int, optional): Number of concepts retained when checking similarity to text. Defaults to 10.
|
|
363
|
+
threshold (float, optional): Minimum similarity for considering concepts. Defaults to 0.3.
|
|
364
|
+
weight_dev (Dict[int, float], optional): Dictionary for setting sentence weights different from 1. Defaults to ``{0: 1.5}``.
|
|
365
|
+
kparm (float, optional): Saturation parameter for aggregating similarities. Defaults to 1.0.
|
|
366
|
+
"""
|
|
367
|
+
if weight_dev is None:
|
|
368
|
+
weight_dev = {0: 1.5}
|
|
369
|
+
self.top_k = top_k
|
|
370
|
+
self.threshold = threshold
|
|
371
|
+
self.weight_dev = weight_dev
|
|
372
|
+
self.kparm = kparm
|
|
373
|
+
|
|
374
|
+
def score_concepts_overall(self) -> None:
|
|
375
|
+
"""Finds concepts by simple similarity search for the whole text and stores them in attribute :pyattr:`overall_concepts`
|
|
376
|
+
as a dict with concept key as key and similarity score as value.
|
|
377
|
+
"""
|
|
378
|
+
self.overall_concepts = {
|
|
379
|
+
x[0]: x[2]
|
|
380
|
+
for x in self.taxonomy.get_concepts_in_text(self.doc_text, top_k=self.top_k, threshold=self.threshold)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
def score_concepts_sentencewise(self) -> None:
|
|
384
|
+
"""Finds concepts by sentencewise similarity search and stores them in attribute :pyattr:`sentences_concepts`
|
|
385
|
+
as a dict with concept key as key and a list of similarity scores for affected sentences, taking ``weight_dev`` into consideration.
|
|
386
|
+
"""
|
|
387
|
+
language = _ISO_TO_NLTK_LANGUAGE.get(self.taxonomy.default_lang, "english")
|
|
388
|
+
sentences = sent_tokenize(self.doc_text, language=language)
|
|
389
|
+
weights = [1] * len(sentences)
|
|
390
|
+
for i, g in self.weight_dev.items():
|
|
391
|
+
if i < len(sentences):
|
|
392
|
+
weights[i] = g
|
|
393
|
+
concepts_scores = defaultdict(list)
|
|
394
|
+
for w, s in zip(weights, sentences):
|
|
395
|
+
for c in self.taxonomy.get_concepts_in_text(s, top_k=self.top_k, threshold=self.threshold):
|
|
396
|
+
concepts_scores[c[0]].append(w * c[2])
|
|
397
|
+
self.sentences_concepts = dict(concepts_scores)
|
|
398
|
+
|
|
399
|
+
def aggregate_sentence_scores(self, min_agg_score: float = 0) -> None:
|
|
400
|
+
"""Identifies those of the concepts found by sentencewise similarity search which exceed a certain score after score aggregation
|
|
401
|
+
and stores them in attribute :pyattr:`aggregated_concepts` as a dict with concept key as key and aggregated score as value.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
min_agg_score (float, optional): Minimum score after sentence aggregation. Defaults to 0.
|
|
405
|
+
"""
|
|
406
|
+
self.score_concepts_sentencewise()
|
|
407
|
+
a_concepts = {}
|
|
408
|
+
for c, v in self.sentences_concepts.items():
|
|
409
|
+
a_score = 0
|
|
410
|
+
for i, score in enumerate(sorted(v, reverse=True)):
|
|
411
|
+
a_score += score * self.kparm / (i + self.kparm) / (i + 1 + self.kparm)
|
|
412
|
+
if a_score >= min_agg_score:
|
|
413
|
+
a_concepts[c] = a_score
|
|
414
|
+
self.aggregated_concepts = a_concepts
|
|
415
|
+
|
|
416
|
+
def find_best_paths(self, concept: str, n: int) -> List[List[str]]:
|
|
417
|
+
"""Finds paths from a given concept to the root of the taxonomy hierarchy along which the mean of aggregated concept scores is greatest.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
concept (str): Key of concept.
|
|
421
|
+
n (int): Number of paths to be returned.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
list: List of paths, each path being a list of concept keys.
|
|
425
|
+
"""
|
|
426
|
+
scored_paths = [(p, sum([self.aggregated_concepts.get(ci, 0) for ci in p]) / len(p))
|
|
427
|
+
for p in self.taxonomy.get_parent_concepts(concept)
|
|
428
|
+
]
|
|
429
|
+
sorted_paths = sorted(scored_paths, key=lambda x: x[1], reverse=True)
|
|
430
|
+
return [x[0] for x in sorted_paths[:n]]
|
|
431
|
+
|
|
432
|
+
def evaluate_hierarchy(self, max_paths: int = 2) -> None:
|
|
433
|
+
"""Shares aggregated scores of concepts with their hierarchical predecessors and stores the results in attribute :pyattr:`hierarchy_distribution` as
|
|
434
|
+
a dict of dicts, where the outer keys are hierarchy levels and the inner keys are concept keys, followed by the summed-up score shares.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
max_paths (int, optional): Number of paths to the root considered per concept. Defaults to 2.
|
|
438
|
+
"""
|
|
439
|
+
levels = defaultdict(lambda: defaultdict(float))
|
|
440
|
+
for c, asc in self.aggregated_concepts.items():
|
|
441
|
+
cps = self.find_best_paths(c, max_paths)
|
|
442
|
+
for cp in cps:
|
|
443
|
+
for i, ci in enumerate(reversed(cp[:-1])):
|
|
444
|
+
levels[i][ci] += asc / len(cps)
|
|
445
|
+
self.hierarchy_distribution = {i: {c: v for c, v in cs.items()} for i, cs in levels.items()}
|
|
446
|
+
|
|
447
|
+
def restrict_hierarchy(self, level: int, percentage: float) -> None:
|
|
448
|
+
"""Forms truncated lists of highest-scored concepts of a given hierarchy level such that the summed-up hierarchical scores of the remaining
|
|
449
|
+
concepts exceed a given percentage of the total summed-up score, and stores the results in attribute :pyattr:`hierarchy_restriction` as a dict
|
|
450
|
+
with the level number as key and the truncated list of concept keys as value.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
level (int): Hierarchy level.
|
|
454
|
+
percentage (float): Percentage of the total summed-up score that the remaining concepts must exceed.
|
|
455
|
+
"""
|
|
456
|
+
if self.hierarchy_distribution.get(level):
|
|
457
|
+
sum_list = []
|
|
458
|
+
list_sum = 0
|
|
459
|
+
for elem in sorted(self.hierarchy_distribution[level].items(), key=lambda x: x[1], reverse=True):
|
|
460
|
+
sum_list.append((elem, list_sum))
|
|
461
|
+
list_sum += elem[1]
|
|
462
|
+
self.hierarchy_restriction[level] = [y[0][0] for y in sum_list if y[1] / list_sum * 100 < percentage]
|
|
463
|
+
else:
|
|
464
|
+
self.hierarchy_restriction[level] = []
|
|
465
|
+
|
|
466
|
+
def get_relevant_concepts(
|
|
467
|
+
self,
|
|
468
|
+
min_score: Optional[float] = None,
|
|
469
|
+
max_number: int = 100,
|
|
470
|
+
) -> List[Tuple[List[str], float]]:
|
|
471
|
+
"""Returns the hierarchical paths to all concepts exceeding a minimum aggregated score, as long as the steps of these paths are contained
|
|
472
|
+
in the truncated level lists of concepts in ``hierarchy_restriction``.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
min_score (float, optional): Minimum aggregated score. Defaults to ``None``, in which case the class attribute ``threshold`` is used.
|
|
476
|
+
max_number (int, optional): Maximum number of concept paths returned. Defaults to 100.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
list: List of resulting concept paths, where each concept path is a pair of the list of concept keys constituting the path (root-first) and the aggregated score.
|
|
480
|
+
"""
|
|
481
|
+
if min_score is None:
|
|
482
|
+
min_score = self.threshold
|
|
483
|
+
concept_paths = sorted(
|
|
484
|
+
[(self.taxonomy.get_parent_concepts(c), s) for c, s in self.aggregated_concepts.items() if s >= min_score],
|
|
485
|
+
key=lambda x: x[1], reverse=True
|
|
486
|
+
)[:max_number]
|
|
487
|
+
concept_paths = [(cp[-2::-1], cps[1]) for cps in concept_paths for cp in cps[0]]
|
|
488
|
+
for level, rest in self.hierarchy_restriction.items():
|
|
489
|
+
concept_paths = [cp for cp in concept_paths if set(cp[0][level:level + 1]).issubset(set(rest))]
|
|
490
|
+
return concept_paths
|
|
491
|
+
|
|
492
|
+
def identify_concepts(
|
|
493
|
+
self,
|
|
494
|
+
top_k: int,
|
|
495
|
+
min_similarity: float,
|
|
496
|
+
title_weight: float,
|
|
497
|
+
saturation: float,
|
|
498
|
+
min_agg: float,
|
|
499
|
+
top_percent: float,
|
|
500
|
+
second_percent: float,
|
|
501
|
+
max_baseconcepts: int,
|
|
502
|
+
max_paths: int,
|
|
503
|
+
with_scores: bool = False,
|
|
504
|
+
) -> List[List[Union[Tuple[str, str], Tuple[str, str, float]]]]:
|
|
505
|
+
"""Convenience function for identifying concepts, given the various parameters of the algorithm.
|
|
506
|
+
|
|
507
|
+
Args:
|
|
508
|
+
top_k (int): Number of concepts retained when checking similarity to text.
|
|
509
|
+
min_similarity (float): Minimum value for similarity on sentence level.
|
|
510
|
+
title_weight (float): Weight for first sentence (which is supposed to be the title of the document).
|
|
511
|
+
saturation (float): Saturation parameter for score aggregation.
|
|
512
|
+
min_agg (float): Minimum value for aggregated score.
|
|
513
|
+
top_percent (float): Percentage for hierarchy truncation on top level.
|
|
514
|
+
second_percent (float): Percentage for hierarchy truncation on second level.
|
|
515
|
+
max_baseconcepts (int): Maximum number of returned (leaf) concepts.
|
|
516
|
+
max_paths (int): Number of paths to the root considered per concept.
|
|
517
|
+
with_scores (bool, optional): Whether to include scores. Defaults to ``False``.
|
|
518
|
+
|
|
519
|
+
Returns:
|
|
520
|
+
list: List of lists of ``(concept name, concept key)`` pairs, or ``(concept name, concept key, aggregated score)`` triples when ``with_scores`` is ``True``.
|
|
521
|
+
"""
|
|
522
|
+
self.set_parameters(top_k=top_k, threshold=min_similarity, weight_dev={0: title_weight}, kparm=saturation)
|
|
523
|
+
self.aggregate_sentence_scores(min_agg_score=min_agg)
|
|
524
|
+
self.evaluate_hierarchy(max_paths=max_paths)
|
|
525
|
+
self.restrict_hierarchy(0, top_percent)
|
|
526
|
+
self.restrict_hierarchy(1, second_percent)
|
|
527
|
+
relevant_concepts_and_scores = self.get_relevant_concepts(min_score=min_agg, max_number=max_baseconcepts)
|
|
528
|
+
relevant_concepts = [rc[0] for rc in relevant_concepts_and_scores]
|
|
529
|
+
relevant_scores = [rc[1] for rc in relevant_concepts_and_scores]
|
|
530
|
+
if not with_scores:
|
|
531
|
+
return self.taxonomy.show_concepts(relevant_concepts)
|
|
532
|
+
else:
|
|
533
|
+
return self.taxonomy.show_concepts(relevant_concepts, scores=relevant_scores)
|
taxo-1.0.0/usage.ipynb
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"id": "f2ccd734",
|
|
6
|
+
"metadata": {},
|
|
7
|
+
"source": "## taxo\n\nThis notebook demonstrates the use of taxo."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"cell_type": "markdown",
|
|
11
|
+
"id": "ae9b02d2",
|
|
12
|
+
"metadata": {},
|
|
13
|
+
"source": [
|
|
14
|
+
"First import the packages used."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"cell_type": "code",
|
|
19
|
+
"execution_count": 3,
|
|
20
|
+
"id": "19b68fc3",
|
|
21
|
+
"metadata": {},
|
|
22
|
+
"outputs": [],
|
|
23
|
+
"source": [
|
|
24
|
+
"import pandas as pd\n",
|
|
25
|
+
"import networkx as nx\n",
|
|
26
|
+
"from sentence_transformers import SentenceTransformer\n",
|
|
27
|
+
"\n",
|
|
28
|
+
"import taxo"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"cell_type": "markdown",
|
|
33
|
+
"id": "186799df",
|
|
34
|
+
"metadata": {},
|
|
35
|
+
"source": [
|
|
36
|
+
"Then fill a pandas dataframe with a taxonomy, for instance by loading it from an excel sheet.\n",
|
|
37
|
+
"\n",
|
|
38
|
+
"Each row corresponds to a taxonomy concept. Each concept has to have a key, a label_en, a description_en (optionally also label and description in other languages), and a parents attribute, which is a string that interprets as the list of its parent concepts (note that not only trees but also more general DAGs are allowed as taxonomic hierarchies).\n",
|
|
39
|
+
"\n",
|
|
40
|
+
"There has to be on special formal concept which is the top parent of all other concepts."
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"cell_type": "code",
|
|
45
|
+
"execution_count": 4,
|
|
46
|
+
"id": "4707d994",
|
|
47
|
+
"metadata": {},
|
|
48
|
+
"outputs": [],
|
|
49
|
+
"source": [
|
|
50
|
+
"df_media_tax = pd.read_excel(\"media.xlsx\")\n",
|
|
51
|
+
"df_media_tax.fillna(\"\", inplace=True)"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"cell_type": "markdown",
|
|
56
|
+
"id": "b5bf204a",
|
|
57
|
+
"metadata": {},
|
|
58
|
+
"source": [
|
|
59
|
+
"The example contained in the file `media.xlsx` is extracted from the IPTC media topics taxonomy, a taxonomy often used for subject classification of news articles.\n",
|
|
60
|
+
"\n",
|
|
61
|
+
"Note that the top parent node has the key \"M00000000\".\n",
|
|
62
|
+
"\n",
|
|
63
|
+
"In the following, we use the embedding model [all-MiniLM-L6-v2](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)."
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"cell_type": "code",
|
|
68
|
+
"execution_count": 5,
|
|
69
|
+
"id": "49c013c5",
|
|
70
|
+
"metadata": {},
|
|
71
|
+
"outputs": [
|
|
72
|
+
{
|
|
73
|
+
"data": {
|
|
74
|
+
"text/plain": [
|
|
75
|
+
"<taxo.ConceptTaxonomy at 0x21ce1ad3770>"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"execution_count": 5,
|
|
79
|
+
"metadata": {},
|
|
80
|
+
"output_type": "execute_result"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"source": [
|
|
84
|
+
"media_tax = taxo.ConceptTaxonomy(nx.DiGraph(), \"M00000000\", transformer=SentenceTransformer(\"all-MiniLM-L6-v2\"))\n",
|
|
85
|
+
"media_tax.from_df(df_media_tax)"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"cell_type": "markdown",
|
|
90
|
+
"id": "a4cc827d",
|
|
91
|
+
"metadata": {},
|
|
92
|
+
"source": [
|
|
93
|
+
"Now, given an example text ..."
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"cell_type": "code",
|
|
98
|
+
"execution_count": 6,
|
|
99
|
+
"id": "fbd75c2f",
|
|
100
|
+
"metadata": {},
|
|
101
|
+
"outputs": [],
|
|
102
|
+
"source": [
|
|
103
|
+
"text = \"\"\"\n",
|
|
104
|
+
"AI opens up new possibilities for air traffic control and the cockpit.\n",
|
|
105
|
+
"\n",
|
|
106
|
+
"How will artificial intelligence (AI) support air traffic controllers and pilots in the future? Over the past four years, \n",
|
|
107
|
+
"the German Aerospace Center (Deutsches Zentrum für Luft- und Raumfahrt; DLR) has been investigating this question as part \n",
|
|
108
|
+
"of the LOKI project (Kollaboration von Luftfahrt-OperateurInnen und KI-Systemen, or 'Collaboration between Aviation \n",
|
|
109
|
+
"Operators and AI Systems' in English). In collaboration with national and international companies and research \n",
|
|
110
|
+
"institutions from the air traffic control sector, as well as public authorities, the project also developed relevant \n",
|
|
111
|
+
"recommendations for introducing AI into aviation.\n",
|
|
112
|
+
"\n",
|
|
113
|
+
"The aviation industry is currently working intensively on introducing AI systems. Unlike conventional assistance systems, \n",
|
|
114
|
+
"these can process large volumes of data, be trained on the basis of this data and so adapt flexibly to new situations. \n",
|
|
115
|
+
"AI systems offer the potential to increase the efficiency of air traffic and relieve the workload on highly qualified \n",
|
|
116
|
+
"specialists, while at the same time ensuring safety, transparency and human control at all times.\n",
|
|
117
|
+
"\n",
|
|
118
|
+
"\"In the DLR LOKI project, we investigated how humans and AI systems can cooperate successfully in aviation. Our aim was to \n",
|
|
119
|
+
"make this collaboration trustworthy, transparent and user-centred,\" says the project’s scientific lead, Carmen Bruder from \n",
|
|
120
|
+
"the DLR Institute of Aerospace Medicine.\n",
|
|
121
|
+
"\n",
|
|
122
|
+
"The researchers developed a digital air traffic controller as an AI-supported team member for air traffic control and an \n",
|
|
123
|
+
"intelligent assistance system for pilots, and made recommendations for the safe introduction of trustworthy AI into aviation.\n",
|
|
124
|
+
"\n",
|
|
125
|
+
"One focus of the research was the development of the digital air traffic controller DIRC (Digital Interactive Reliable \n",
|
|
126
|
+
"Controller). Unlike traditional assistance systems, the DIRC not only supports human controllers with information or \n",
|
|
127
|
+
"recommendations but can also take on certain tasks independently. This allows for dynamic coordination with human air traffic \n",
|
|
128
|
+
"controllers regarding which tasks are to be undertaken.\n",
|
|
129
|
+
"\n",
|
|
130
|
+
"Simulations showed that human and digital air traffic controllers can divide their tasks efficiently. Together, they managed \n",
|
|
131
|
+
"up to 25 percent more than the current maximum traffic volume specified for an airspace sector. The research thus addresses \n",
|
|
132
|
+
"a key challenge facing European air traffic: rising traffic volumes coupled with the increasing challenge of maintaining \n",
|
|
133
|
+
"staffing levels in air traffic control.\n",
|
|
134
|
+
"\n",
|
|
135
|
+
"In addition to air traffic control, the researchers developed the Intelligent Pilot Assistance System (IPAS), a demonstrator \n",
|
|
136
|
+
"designed to support pilots in the cockpit.\n",
|
|
137
|
+
"\n",
|
|
138
|
+
"The system is designed to support crews particularly in time-critical situations, for example when selecting alternative \n",
|
|
139
|
+
"flight routes or suitable alternate airports. The AI analyses large amounts of relevant information and processes it to aid \n",
|
|
140
|
+
"decision-making. At the same time, the team investigated what information pilots need in order to understand and reliably \n",
|
|
141
|
+
"assess the recommendations of an AI system.\n",
|
|
142
|
+
"\n",
|
|
143
|
+
"In addition to technical developments, the project investigated the conditions necessary for trustworthy collaboration \n",
|
|
144
|
+
"between humans and AI systems. The researchers analysed the expectations and requirements of aviation personnel and assessed \n",
|
|
145
|
+
"how collaboration between humans and AI in aviation should be structured.\n",
|
|
146
|
+
"\n",
|
|
147
|
+
"A key finding is that trust is not built solely on a system’s performance. Crucial factors include transparent information \n",
|
|
148
|
+
"about the AI's capabilities and limitations, suitable training opportunities and the gradual introduction of new functions. \n",
|
|
149
|
+
"Based on the project findings, important recommendations were formulated; taking these into account will help ensure the \n",
|
|
150
|
+
"successful development and introduction of AI systems in aviation.\n",
|
|
151
|
+
"\n",
|
|
152
|
+
"The findings form a basis for the further development of AI systems in aviation and will feed into the European debate on the \n",
|
|
153
|
+
"safe introduction of such systems.\n",
|
|
154
|
+
"\"\"\""
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"cell_type": "markdown",
|
|
159
|
+
"id": "4432982c",
|
|
160
|
+
"metadata": {},
|
|
161
|
+
"source": [
|
|
162
|
+
"... we can prepare it for tagging."
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"cell_type": "code",
|
|
167
|
+
"execution_count": 7,
|
|
168
|
+
"id": "10997545",
|
|
169
|
+
"metadata": {},
|
|
170
|
+
"outputs": [],
|
|
171
|
+
"source": [
|
|
172
|
+
"taxed_document = taxo.TaxedDocument(media_tax, text)"
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"cell_type": "markdown",
|
|
177
|
+
"id": "6024862e",
|
|
178
|
+
"metadata": {},
|
|
179
|
+
"source": [
|
|
180
|
+
"The following set of hyperparameters typically works well with this taxonomy. Other taxonomies might require tuning of these parameters."
|
|
181
|
+
]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"cell_type": "code",
|
|
185
|
+
"execution_count": 8,
|
|
186
|
+
"id": "3dc3a401",
|
|
187
|
+
"metadata": {},
|
|
188
|
+
"outputs": [],
|
|
189
|
+
"source": [
|
|
190
|
+
"tagging_parameters = (\n",
|
|
191
|
+
" 10, # Max number of concepts initially assigned to each sentence\n",
|
|
192
|
+
" 0.25, # Minimum similarity value on sentence level\n",
|
|
193
|
+
" 2, # Weight for title sentence\n",
|
|
194
|
+
" 1.5, # Saturation parameter for score aggregation\n",
|
|
195
|
+
" 0.25, # Minimum value for aggregated score \n",
|
|
196
|
+
" 80, # Percentage for hierarchy truncation on top level\n",
|
|
197
|
+
" 80, # Percentage for hierarchy truncation on second level\n",
|
|
198
|
+
" 7, # Maximum number of returned leaf concepts \n",
|
|
199
|
+
" 2, # Number of paths to the root considered per concept\n",
|
|
200
|
+
")"
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"cell_type": "markdown",
|
|
205
|
+
"id": "4c00e4f1",
|
|
206
|
+
"metadata": {},
|
|
207
|
+
"source": [
|
|
208
|
+
"Now we are ready to identify the taxonomy tags for the given text. The result shows the highest scored concepts including their hierarchical anchestry."
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"cell_type": "code",
|
|
213
|
+
"execution_count": 9,
|
|
214
|
+
"id": "24e9f432",
|
|
215
|
+
"metadata": {},
|
|
216
|
+
"outputs": [
|
|
217
|
+
{
|
|
218
|
+
"data": {
|
|
219
|
+
"text/plain": [
|
|
220
|
+
"[[('economy, business and finance', 'M04000000', 0.572107398702342),\n",
|
|
221
|
+
" ('economic sector', 'M20000209', 0.572107398702342),\n",
|
|
222
|
+
" ('transport', 'M20000337', 0.572107398702342),\n",
|
|
223
|
+
" ('air transport', 'M20000338', 0.572107398702342)],\n",
|
|
224
|
+
" [('science and technology', 'M13000000', 0.4709945980217274),\n",
|
|
225
|
+
" ('technology and engineering', 'M20000756', 0.4709945980217274),\n",
|
|
226
|
+
" ('information technology and computer science',\n",
|
|
227
|
+
" 'M20000763',\n",
|
|
228
|
+
" 0.4709945980217274),\n",
|
|
229
|
+
" ('artificial intelligence', 'M20001298', 0.4709945980217274)],\n",
|
|
230
|
+
" [('disaster, accident and emergency incident',\n",
|
|
231
|
+
" 'M03000000',\n",
|
|
232
|
+
" 0.4322114299130465),\n",
|
|
233
|
+
" ('accident and emergency incident', 'M20000139', 0.4322114299130465),\n",
|
|
234
|
+
" ('transportation accident and incident', 'M20000143', 0.4322114299130465),\n",
|
|
235
|
+
" ('air and space accident and incident', 'M20000144', 0.4322114299130465)],\n",
|
|
236
|
+
" [('science and technology', 'M13000000', 0.4314064954667891),\n",
|
|
237
|
+
" ('technology and engineering', 'M20000756', 0.4314064954667891),\n",
|
|
238
|
+
" ('aerospace engineering', 'M20000757', 0.4314064954667891)],\n",
|
|
239
|
+
" [('science and technology', 'M13000000', 0.42194015078626435),\n",
|
|
240
|
+
" ('scientific research', 'M20000735', 0.42194015078626435),\n",
|
|
241
|
+
" ('scientific innovation', 'M20000736', 0.42194015078626435)],\n",
|
|
242
|
+
" [('disaster, accident and emergency incident',\n",
|
|
243
|
+
" 'M03000000',\n",
|
|
244
|
+
" 0.3640833247315276),\n",
|
|
245
|
+
" ('emergency incident', 'M20000160', 0.3640833247315276),\n",
|
|
246
|
+
" ('transport incident', 'M20000162', 0.3640833247315276),\n",
|
|
247
|
+
" ('air and space incident', 'M20000163', 0.3640833247315276)]]"
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
"execution_count": 9,
|
|
251
|
+
"metadata": {},
|
|
252
|
+
"output_type": "execute_result"
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
"source": [
|
|
256
|
+
"taxed_document.identify_concepts(*tagging_parameters, with_scores=True)"
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
],
|
|
260
|
+
"metadata": {
|
|
261
|
+
"kernelspec": {
|
|
262
|
+
"display_name": ".venv",
|
|
263
|
+
"language": "python",
|
|
264
|
+
"name": "python3"
|
|
265
|
+
},
|
|
266
|
+
"language_info": {
|
|
267
|
+
"codemirror_mode": {
|
|
268
|
+
"name": "ipython",
|
|
269
|
+
"version": 3
|
|
270
|
+
},
|
|
271
|
+
"file_extension": ".py",
|
|
272
|
+
"mimetype": "text/x-python",
|
|
273
|
+
"name": "python",
|
|
274
|
+
"nbconvert_exporter": "python",
|
|
275
|
+
"pygments_lexer": "ipython3",
|
|
276
|
+
"version": "3.13.3"
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
"nbformat": 4,
|
|
280
|
+
"nbformat_minor": 5
|
|
281
|
+
}
|