judy4444 commited on
Commit
edad764
·
verified ·
1 Parent(s): 07a0bf2

Upload 43 files

Browse files
Files changed (43) hide show
  1. README.rst +175 -0
  2. nlp.py +0 -0
  3. requirements.txt +80 -0
  4. src/.DS_Store +0 -0
  5. src/__init__.py +0 -0
  6. src/__pycache__/__init__.cpython-311.pyc +0 -0
  7. src/__pycache__/classes.cpython-311.pyc +0 -0
  8. src/__pycache__/conll_output.cpython-311.pyc +0 -0
  9. src/__pycache__/data_preparation.cpython-311.pyc +0 -0
  10. src/__pycache__/logger.cpython-311.pyc +0 -0
  11. src/classes.py +70 -0
  12. src/conll_output.py +50 -0
  13. src/data_preparation.py +199 -0
  14. src/dependency_parser/__init__.py +0 -0
  15. src/dependency_parser/__pycache__/__init__.cpython-311.pyc +0 -0
  16. src/dependency_parser/__pycache__/biaff_parser.cpython-311.pyc +0 -0
  17. src/dependency_parser/biaff_parser.py +50 -0
  18. src/initialize_disambiguator/__init__.py +0 -0
  19. src/initialize_disambiguator/__pycache__/__init__.cpython-311.pyc +0 -0
  20. src/initialize_disambiguator/__pycache__/bert_disambiguator.cpython-311.pyc +0 -0
  21. src/initialize_disambiguator/__pycache__/disambiguator_interface.cpython-311.pyc +0 -0
  22. src/initialize_disambiguator/__pycache__/mle_disambiguator.cpython-311.pyc +0 -0
  23. src/initialize_disambiguator/bert_disambiguator.py +11 -0
  24. src/initialize_disambiguator/disambiguator_interface.py +28 -0
  25. src/initialize_disambiguator/mle_disambiguator.py +17 -0
  26. src/logger.py +46 -0
  27. src/parse_disambiguation/__init__.py +0 -0
  28. src/parse_disambiguation/__pycache__/__init__.cpython-311.pyc +0 -0
  29. src/parse_disambiguation/__pycache__/disambiguation_analysis.cpython-311.pyc +0 -0
  30. src/parse_disambiguation/__pycache__/feature_extraction.cpython-311.pyc +0 -0
  31. src/parse_disambiguation/disambiguation_analysis.py +69 -0
  32. src/parse_disambiguation/feature_extraction.py +212 -0
  33. src/utils/__init__.py +0 -0
  34. src/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  35. src/utils/__pycache__/conll_fixes.cpython-311.pyc +0 -0
  36. src/utils/__pycache__/model_downloader.cpython-311.pyc +0 -0
  37. src/utils/__pycache__/text_cleaner.cpython-311.pyc +0 -0
  38. src/utils/conll_fixes.py +33 -0
  39. src/utils/model_downloader.py +33 -0
  40. src/utils/text_cleaner.py +34 -0
  41. tests/test_feats.tsv +3 -0
  42. tests/test_feature_extraction.py +55 -0
  43. text_to_conll_cli.py +110 -0
README.rst ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ NLP : - TEXT TO KNOWLEDGE GRAPH
2
+ we use the CamelParser to process it and extract the knowledge graph in JSON format.
3
+ Then we use RULE-BASED Resoning to Extract Entities and Relations from the text and build a knowledge graph in JSON format.
4
+
5
+ CamelParser
6
+ =============
7
+
8
+ .. image:: https://img.shields.io/pypi/l/camel-tools.svg
9
+ :target: https://opensource.org/licenses/MIT
10
+ :alt: MIT License
11
+
12
+ Introduction
13
+ ------------
14
+
15
+ CamelParser is an open-source Python-based Arabic dependency parser targeting two popular
16
+ Arabic dependency formalisms, the Columbia Arabic Treebank (CATiB), and Universal Dependencies (UD).
17
+
18
+ The CamelParser pipeline handles the processing of raw text and produces tokenization,
19
+ part-of-speech and rich morphological features. For disambiguation, users can choose between
20
+ the BERT unfactored disambiguator, or a lighter Maximum Likelihood Estimation (MLE) disambiguator,
21
+ both of which are included in CAMeL Tools. For dependency parsing, we use the SuPar Biaffine Dependency Parser.
22
+
23
+
24
+ Installation
25
+ ------------
26
+ 1. Clone this repo
27
+
28
+ 2. Set up a virtual environment using Python 3.11.13 (you can follow the tutorial `here <https://www.youtube.com/watch?si=g2zx-D0zdkL50knb&v=31WU0Dhw4sk&feature=youtu.be>`_).
29
+
30
+ Currently, CamelParser does not work with later versions of python due to issues with some dependencies.
31
+
32
+ 3. Install the required packages:
33
+
34
+ .. code-block:: bash
35
+
36
+ pip install -r requirements.txt
37
+
38
+ 4. Download dependency parsing models:
39
+
40
+ .. code-block:: bash
41
+
42
+ python download_models.py
43
+
44
+ Currently, two Arabic script models, CATiB and UD, will be downloaded from the CAMeL Lab's
45
+ `parser models collection <https://huggingface.co/collections/CAMeL-Lab/camelparser-654a3df21f70b3b5e72f95d9>`_
46
+ on Hugging Face. More models will be added soon!
47
+
48
+ Examples
49
+ --------
50
+ The CamelParser allows users to pass either a string or a file containing **one or more** sentences.
51
+ Below are examples using the different *string* inputs that CamelParser accepts.
52
+ We pass each example as a string using -s.
53
+ However, we do recommend using the file method (-i) along with the path to the file
54
+ when passing multiple sentences.
55
+
56
+ You can also refer to the **sample_starting_point.py** to use the parser in your code, or for more advanced usage:
57
+
58
+ .. code-block:: bash
59
+
60
+ text_to_conll_cli.py
61
+
62
+ handle_multiple_texts.py
63
+
64
+ handle_multiple_conll_files.py
65
+
66
+ Passing text
67
+
68
+ .. code-block:: bash
69
+
70
+ python text_to_conll_cli.py -f text -s "جامعة نيويورك أبو ظبي تنشر أول أطلس لكوكب المريخ باللغة العربية."
71
+
72
+ The verbose version of the above example (default values are shown)
73
+
74
+ .. code-block:: bash
75
+
76
+ python text_to_conll_cli.py -f text -b r13 -d bert -m catib -s "جامعة نيويورك أبو ظبي تنشر أول أطلس لكوكب المريخ باللغة العربية."
77
+
78
+ Passing preprocessed text (cleaned and whitespace tokenized)
79
+
80
+ .. code-block:: bash
81
+
82
+ python text_to_conll_cli.py -f preprocessed_text -s "جامعة نيويورك أبو ظبي تنشر أول أطلس لكوكب المريخ باللغة العربية ."
83
+
84
+ *Note that the difference between the -f text and preprocessed_text parser input settings is*
85
+ *that for text we use different utilities from CAMeL Tools to*
86
+ `normalize unicode <https://camel-tools.readthedocs.io/en/latest/api/utils/normalize.html?highlight=normalize_unicode#camel_tools.utils.normalize.normalize_unicode>`_,
87
+ `dediactritize <https://camel-tools.readthedocs.io/en/latest/api/utils/dediac.html?highlight=dediac_ar>`_,
88
+ *clean the text using*
89
+ `arclean <https://camel-tools.readthedocs.io/en/latest/api/utils/charmap.html?highlight=arclean#utility>`_,
90
+ *and perform*
91
+ `whitespace tokenization <https://camel-tools.readthedocs.io/en/latest/api/tokenizers/word.html?highlight=simple_word_tokenize#camel_tools.tokenizers.word.simple_word_tokenize>`_.
92
+
93
+ tokenized is used when 1) the text has already been tokenized, and 2) only dependency relations are needed;
94
+ the POS tags and features will not be generated.
95
+
96
+ .. code-block:: bash
97
+
98
+ python text_to_conll_cli.py -f tokenized -s "جامعة نيويورك أبو ظبي تنشر أول أطلس ل+ كوكب المريخ ب+ اللغة العربية ."
99
+
100
+ tokenized_tagged is used when the user has the tokens and POS tags. They should be passed as tuples.
101
+
102
+ .. code-block:: bash
103
+
104
+ python text_to_conll_cli.py -f tokenized_tagged -s "(جامعة, NOM) (نيويورك, PROP) (أبو, PROP) (ظبي, PROP) (تنشر, VRB) (أول, NOM) (أطلس, NOM) (ل+, PRT) (كوكب, NOM) (المريخ, PROP) (ب+, PRT) (اللغة, NOM) (العربية, NOM) (., PNX)"
105
+
106
+
107
+ Using a custom model
108
+ ------------------
109
+ You can use your own dependency parser models by
110
+
111
+ 1. placing the model in the models directory (this directory is created when you run download_models.py, but you can create it yourself)
112
+
113
+ 2. place your model in the models directory
114
+
115
+ 3. when running one of the scripts, add -m [model_name]. Just type the model name WITHOUT the path.
116
+
117
+ Extending the code
118
+ ------------------
119
+
120
+ You can also use different parts of the code to create your own pipeline.
121
+ The handle_multiple_texts.py is an example of that. It can be used to parse a directory of text files,
122
+ saving the resulting CoNLL-X files to a given output directory.
123
+
124
+ Using another morphology database
125
+ ---------------------------------
126
+
127
+ Curently, the CamelParser uses CAMeLTools' default morphology database, the morphology-db-msa-r13.
128
+
129
+ For our paper, we used the calima-msa-s31 database. To use this database,
130
+ follow these steps (note that you need an account with the LDC):
131
+
132
+
133
+ 1. Install camel_tools v1.5.6 or later (you can check this using camel_data -v)
134
+
135
+ 2. Download the camel data for the BERT unfactored (MSA) model, as well as the morphology database:
136
+
137
+ .. code-block:: bash
138
+
139
+ camel_data -i morphology-db-msa-s31
140
+ camel_data -i disambig-bert-unfactored-msa
141
+
142
+ 3. Download the LDC2010L01 from the ldc downloads:
143
+ - go to https://catalog.ldc.upenn.edu/organization/downloads
144
+ - search for LDC2010L01.tgz and download it
145
+
146
+ 4. DO NOT EXTRACT LDC2010L01.tgz! We'll use the following command from camel tools to install the db:
147
+
148
+ .. code-block:: bash
149
+
150
+ camel_data -p morphology-db-msa-s31 /path/to/LDC2010L01.tgz
151
+
152
+ 5. When running the main script, use -b and pass calima-msa-s31.
153
+
154
+ Reproducing paper results
155
+ -------------------------
156
+
157
+ To reproduce the results in our paper *CamelParser2.0: A State-of-the-Art Dependency Parser for Arabic*, please use the code found
158
+ in the *paper_version* branch.
159
+
160
+ Citation
161
+ --------
162
+
163
+ If you find the CamelParser useful in your research, please cite
164
+
165
+ .. code-block:: bibtex
166
+
167
+ @inproceedings{Elshabrawy:2023:camelparser,
168
+ title = "{CamelParser2.0: A State-of-the-Art Dependency Parser for Arabic}",
169
+ author = {Ahmed Elshabrawy and
170
+ Muhammed AbuOdeh and
171
+ Go Inoue and
172
+ Nizar Habash} ,
173
+ booktitle = {Proceedings of The First Arabic Natural Language Processing Conference (ArabicNLP 2023)},
174
+ year = "2023"
175
+ }
nlp.py ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated-doc==0.0.5
2
+ antlr4-python3-runtime==4.9.3
3
+ anyio==4.14.2
4
+ cachetools==5.3.1
5
+ camel-kenlm==2025.4.8
6
+ camel_tools==1.5.6
7
+ certifi==2023.5.7
8
+ charset-normalizer==3.1.0
9
+ click==8.1.3
10
+ colorama==0.4.6
11
+ defusedxml==0.7.1
12
+ dill==0.3.7
13
+ docopt==0.6.2
14
+ editdistance==0.6.2
15
+ emoji==2.4.0
16
+ exceptiongroup==1.1.3
17
+ filelock==3.12.0
18
+ fsspec==2023.5.0
19
+ future==0.18.3
20
+ h11==0.16.0
21
+ hf-xet==1.6.0
22
+ httpcore==1.0.9
23
+ httpx==0.28.1
24
+ huggingface-hub==0.15.1
25
+ hydra-core==1.3.2
26
+ idna==3.4
27
+ iniconfig==2.0.0
28
+ Jinja2==3.1.2
29
+ joblib==1.2.0
30
+ markdown-it-py==4.2.0
31
+ MarkupSafe==2.1.3
32
+ mdurl==0.1.2
33
+ mpmath==1.3.0
34
+ muddler==0.1.3
35
+ multiprocess==0.70.15
36
+ narwhals==2.24.0
37
+ networkx==3.1
38
+ nltk==3.8.1
39
+ numpy==1.24.3
40
+ omegaconf==2.3.0
41
+ opt-einsum==3.3.0
42
+ packaging==23.1
43
+ pandas==2.0.2
44
+ pathos==0.3.1
45
+ platformdirs==4.11.1
46
+ pluggy==1.2.0
47
+ pox==0.3.3
48
+ ppft==1.7.6.7
49
+ protobuf==4.23.2
50
+ Pygments==2.20.0
51
+ pyrsistent==0.19.3
52
+ pytest==7.4.0
53
+ python-dateutil==2.8.2
54
+ pytz==2023.3
55
+ PyYAML==6.0
56
+ regex==2023.6.3
57
+ requests==2.31.0
58
+ rich==15.0.0
59
+ safetensors==0.8.0
60
+ scikit-learn==1.2.2
61
+ scipy==1.10.1
62
+ shellingham==1.5.4
63
+ six==1.16.0
64
+ stanza==1.5.0
65
+ supar @ git+https://github.com/yzhangcs/parser.git@17ec77dd05aaaa8160e3555134ff5f3b654e4991
66
+ sympy==1.12
67
+ tabulate==0.9.0
68
+ termcolor==3.3.0
69
+ threadpoolctl==3.1.0
70
+ tokenizers==0.13.3
71
+ tomli==2.0.1
72
+ torch==2.0.1
73
+ tqdm==4.65.0
74
+ transformers==4.29.2
75
+ typer==0.27.1
76
+ typing_extensions==4.6.3
77
+ tzdata==2023.3
78
+ udapi==0.5.2
79
+ udtools==0.2.8
80
+ urllib3==2.0.2
src/.DS_Store ADDED
Binary file (8.2 kB). View file
 
src/__init__.py ADDED
File without changes
src/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (148 Bytes). View file
 
src/__pycache__/classes.cpython-311.pyc ADDED
Binary file (4.71 kB). View file
 
src/__pycache__/conll_output.cpython-311.pyc ADDED
Binary file (3.38 kB). View file
 
src/__pycache__/data_preparation.cpython-311.pyc ADDED
Binary file (11.4 kB). View file
 
src/__pycache__/logger.cpython-311.pyc ADDED
Binary file (2.83 kB). View file
 
src/classes.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ from typing import List, Union
3
+ from dataclasses import astuple, dataclass
4
+ import pandas as pd
5
+ from camel_tools.disambig.bert import BERTUnfactoredDisambiguator
6
+ from camel_tools.disambig.mle import MLEDisambiguator
7
+ from camel_tools.utils.charmap import CharMapper
8
+
9
+ def get_conll_tree_header_list():
10
+ return ["ID", "FORM", "LEMMA", "UPOS", "XPOS", "FEATS", "HEAD", "DEPREL", "DEPS", "MISC"]
11
+
12
+ @dataclass
13
+ class ConllParams:
14
+ file_path: str
15
+ parse_model_path: str
16
+
17
+ def __iter__(self):
18
+ return iter(astuple(self))
19
+
20
+ @dataclass
21
+ class TextParams:
22
+ lines: List[str]
23
+ parse_model_path: str
24
+ arclean: CharMapper
25
+ disambiguator_param: Union[BERTUnfactoredDisambiguator, MLEDisambiguator, str]
26
+ clitic_feats_df: pd.DataFrame
27
+ tagset: str
28
+ morphology_db_type: str
29
+
30
+ def __iter__(self):
31
+ return iter(astuple(self))
32
+
33
+ @dataclass
34
+ class PreprocessedTextParams:
35
+ lines: List[str]
36
+ parse_model_path: str
37
+ disambiguator: Union[BERTUnfactoredDisambiguator, MLEDisambiguator, str]
38
+ clitic_feats_df: pd.DataFrame
39
+ tagset: str
40
+ morphology_db_type: str
41
+
42
+ def __iter__(self):
43
+ return iter(astuple(self))
44
+
45
+ @dataclass
46
+ class TokenizedParams:
47
+ lines: List[str]
48
+ parse_model_path: str
49
+
50
+ @dataclass
51
+ class TokenizedTaggedParams:
52
+ lines: List[str]
53
+ parse_model_path: str
54
+
55
+ @dataclass
56
+ class Token:
57
+ ID: int = -1
58
+ FORM: str = '_'
59
+ LEMMA: str = '_'
60
+ UPOS: str = '_'
61
+ XPOS: str = '_'
62
+ FEATS: str = '_'
63
+ HEAD: int = 0
64
+ DEPREL: str = '_'
65
+ DEPS: str = '_'
66
+ MISC: str = '_'
67
+
68
+ def to_conll_row(self):
69
+ col_vals = [str(getattr(self, key)) for key in self.__dataclass_fields__.keys()]
70
+ return '\t'.join(col_vals)
src/conll_output.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ from pathlib import Path
4
+ import re
5
+ from typing import List, Union
6
+
7
+ from .classes import Token
8
+
9
+
10
+ def print_to_conll(string_lines):
11
+ for line in string_lines:
12
+ print(line)
13
+
14
+ def save_to_file(string_lines: List[str], file_path: Path):
15
+ with open(file_path, 'w') as f:
16
+ [f.write(f'{line}\n') for line in string_lines]
17
+
18
+ def text_tuples_to_string(
19
+ text_tuples: List[List[tuple]],
20
+ file_type,
21
+ annotations: Union[List[str], None]=None,
22
+ sentences: Union[List[str], None]=None
23
+ ):
24
+ if sentences is not None and file_type != 'conll':
25
+ # filter out empty lines
26
+ sentences = list(filter(lambda x : len(re.sub(r"\s+", "", x, flags=re.UNICODE)) > 0, sentences))
27
+ # get treeTokens
28
+ tokens = [[tup[1] for tup in sent] for sent in text_tuples]
29
+
30
+ string_lines: List[str] = []
31
+ for i, sentence_tuples in enumerate(text_tuples):
32
+ if file_type == 'conll': # dont add comments to preexisting conll files
33
+ pass
34
+ elif sentences:
35
+ string_lines.extend(
36
+ (
37
+ f"# text = {sentences[i].strip()}",
38
+ f"# treeTokens = {' '.join(tokens[i])}",
39
+ )
40
+ )
41
+ elif annotations:
42
+ string_lines.append(annotations[i])
43
+
44
+ for token_tuple in sentence_tuples:
45
+ token = Token(*token_tuple)
46
+ string_lines.append(token.to_conll_row())
47
+
48
+ string_lines.append('') # add empty line between trees
49
+
50
+ return string_lines
src/data_preparation.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import re
4
+ from typing import List, Union
5
+ import pandas as pd
6
+ from camel_tools.disambig.common import DisambiguatedWord
7
+
8
+ from src.utils.conll_fixes import adjust_eof_newlines
9
+ from .classes import ConllParams, TextParams, PreprocessedTextParams, TokenizedParams, TokenizedTaggedParams
10
+ from .dependency_parser.biaff_parser import parse_conll, parse_text_tuples
11
+ from .initialize_disambiguator.disambiguator_interface import get_disambiguator
12
+ from .parse_disambiguation.disambiguation_analysis import to_sentence_analysis_list
13
+ from .parse_disambiguation.feature_extraction import to_conll_fields_list
14
+ from .utils.text_cleaner import clean_lines, clean_mad, split_lines_words
15
+ from .logger import log
16
+
17
+
18
+ FileTypeParams = Union[ConllParams, TextParams, PreprocessedTextParams, TokenizedParams, TokenizedTaggedParams]
19
+
20
+
21
+ def get_feats_from_text_tuples(text_tuples: List[List[tuple]]) -> List[List[str]]:
22
+ """Extract the FEATS columns from the unparsed data.
23
+ FEATS will exist only for text and pre-processed text inputs.
24
+
25
+ Args:
26
+ text_tuples (List[List[tuple]]): unparsed data
27
+
28
+ Returns:
29
+ List[List[str]]: the FEATS column (or _ if it does not exist)
30
+ """
31
+ try:
32
+ return [[col_items[5] for col_items in tup_list] for tup_list in text_tuples]
33
+ except Exception as e:
34
+ print(e)
35
+ print('Not enough elements in tuple.')
36
+
37
+
38
+ def add_feats(text_tuples: List[List[tuple]], text_feats: List[List[str]]) -> List[List[tuple]]:
39
+ """Add FEATS data to the text tuples.
40
+ The parent list (text_tuples) is a list of sentences.
41
+ Each sentence is a list of tuples.
42
+ Each tuple represents a token.
43
+
44
+ Args:
45
+ text_tuples (List[List[tuple]]): list of list of tuples
46
+ text_feats (List[List[str]]): list of list of FEATS
47
+
48
+ Returns:
49
+ List[List[tuple]]: text_tuples but with the FEATS column filled
50
+ """
51
+ text_tuples_with_feats = []
52
+ for sentence_tuples, sentence_feats in zip(text_tuples, text_feats):
53
+
54
+ # get first 5 and last 4 items from parsed tuple using lists, and add features.
55
+ # Convert the list of fields to a tuple
56
+ merged_tuples = [
57
+ tuple(list(token_tuple[:5]) + [token_feats] + list(token_tuple[6:]))
58
+ for token_tuple, token_feats in zip(sentence_tuples, sentence_feats)
59
+ ]
60
+ text_tuples_with_feats.append(merged_tuples)
61
+ return text_tuples_with_feats
62
+
63
+ def string_to_tuple_list(string_of_tuples: str) -> List[tuple[str, str]]:
64
+ """Take a string of space-separated tuples and convert it to a tuple list.
65
+ Example input: '(جامعة, NOM) (نيويورك, PROP)'
66
+ Example output: [(جامعة, NOM), (نيويورك, PROP)]
67
+
68
+ Args:
69
+ string_of_tuples (str): string of tuples
70
+
71
+ Returns:
72
+ List(tuple[str, str]): list of token-pos tuple pairs
73
+ """
74
+ sentence_tuples = []
75
+
76
+ # split on space, and using positive lookbehind and lookahead
77
+ # to detect parentheses around the space
78
+ for tup in re.split(r'(?<=\)) (?=\()', string_of_tuples.strip()):
79
+ # tup = (جامعة, NOM)
80
+ tup_items = tup[1:-1] # removes parens
81
+ form = (','.join(tup_items.split(',')[:-1])).strip() # account for comma tokens
82
+ pos = (tup_items.split(',')[-1]).strip()
83
+ sentence_tuples.append((form, pos))
84
+ return sentence_tuples
85
+
86
+ def get_tree_tokens(tok_pos_tuples):
87
+ sentences = []
88
+ for sentence_tuples in tok_pos_tuples:
89
+ sentence = ' '.join([tok_pos_tuple[0] for tok_pos_tuple in sentence_tuples])
90
+ sentences.append(sentence)
91
+ return sentences
92
+
93
+ def handle_conll(file_type_params):
94
+ file_path, parse_model_path = file_type_params
95
+ # pass the path to the text file and the model path and name, and get the tuples
96
+ return parse_conll(file_path, parse_model=parse_model_path)
97
+
98
+ @log
99
+ def disambiguate_sentences(disambiguator, token_lines):
100
+ # moved to own function to add to logger
101
+ return disambiguator.disambiguate_sentences(token_lines)
102
+
103
+ def handle_text_types(file_type_params, text_type: str):
104
+ if text_type == 'preprocessed_text':
105
+ lines, _, disambiguator_param, clitic_feats_df, tagset, morphology_db_type = file_type_params
106
+
107
+ token_lines = split_lines_words(lines)
108
+ token_lines = clean_mad(token_lines)
109
+ elif text_type == 'text':
110
+ lines, _, arclean, disambiguator_param, clitic_feats_df, tagset, morphology_db_type = file_type_params
111
+ # clean lines
112
+ token_lines = clean_lines(lines, arclean)
113
+ else:
114
+ assert False, f'Invalid type to process: {text_type}'
115
+
116
+ token_lines = [token_line for token_line in token_lines if token_line]
117
+
118
+ # if str passed, we should create the disambiguator using disambiguator_param and morphology_db_type
119
+ if type(disambiguator_param) == str:
120
+ disambiguator = get_disambiguator(disambiguator_param, morphology_db_type)
121
+ else: # a disambiguator was passed
122
+ disambiguator = disambiguator_param
123
+
124
+ # run the disambiguator on the sentence list to get an analysis for all sentences
125
+ disambiguated_sentences: List[List[DisambiguatedWord]] = disambiguate_sentences(disambiguator, token_lines)
126
+ # get a single analysis for each word (top or tok_match, match not implemented yet)
127
+ # sentence_analysis_list: List[List[dict]] = to_sentence_analysis_list(disambiguated_sentences, selection, selection_criteria)
128
+ sentence_analysis_list: List[List[dict]] = to_sentence_analysis_list(disambiguated_sentences, token_lines)
129
+
130
+ # extract the relevant items from each analysis into conll fields
131
+ return to_conll_fields_list(sentence_analysis_list, clitic_feats_df, tagset)
132
+
133
+
134
+ def handle_preprocessed_text(file_type_params):
135
+ return handle_text_types(file_type_params, 'preprocessed_text')
136
+
137
+ def handle_text(file_type_params):
138
+ return handle_text_types(file_type_params, 'text')
139
+
140
+ def handle_tokenized(file_type_params):
141
+ lines = file_type_params.lines
142
+ # construct tuples before sending them to the parser
143
+ return [[(0, tok, '_' ,'UNK', '_', '_', '_', '_', '_', '_') for tok in line.strip().split(' ')] for line in lines]
144
+
145
+ def handle_tokenized_tagged(file_type_params):
146
+ lines = file_type_params.lines
147
+ # convert input tuple list into a tuple data structure
148
+ tok_pos_tuples_list = [string_to_tuple_list(line) for line in lines]
149
+ # since we did not start with sentences, we make sentences using the tokens (which we call tree tokens)
150
+ lines = get_tree_tokens(tok_pos_tuples_list)
151
+ # construct tuples before sending them to the parser
152
+ return [[(0, tup[0],'_' ,tup[1], '_', '_', '_', '_', '_', '_') for tup in tok_pos_tuples] for tok_pos_tuples in tok_pos_tuples_list]
153
+
154
+ def get_file_type_params(lines, file_type, file_path, parse_model_path,
155
+ arclean, disambiguator_type, clitic_feats_df, tagset, morphology_db_type):
156
+ if file_type == 'conll':
157
+ return ConllParams(file_path, parse_model_path)
158
+ elif file_type == 'text':
159
+ return TextParams(lines, parse_model_path, arclean, disambiguator_type, clitic_feats_df, tagset, morphology_db_type)
160
+ elif file_type == 'preprocessed_text':
161
+ return PreprocessedTextParams(lines, parse_model_path, disambiguator_type, clitic_feats_df, tagset, morphology_db_type)
162
+ elif file_type == 'tokenized':
163
+ return TokenizedParams(lines, parse_model_path)
164
+ elif file_type == 'tokenized_tagged':
165
+ return TokenizedTaggedParams(lines, parse_model_path)
166
+
167
+ def parse_text(file_type: str, file_type_params: FileTypeParams):
168
+ if file_type == 'conll':
169
+ # handle_conll(file_path, parse_model_path)
170
+ adjust_eof_newlines(file_type_params.file_path)
171
+ parsed_text_tuples = handle_conll(file_type_params)
172
+ else:
173
+ text_tuples: List[List[tuple]] = []
174
+ if file_type == 'text':
175
+ text_tuples = handle_text(file_type_params)
176
+ elif file_type == 'preprocessed_text':
177
+ text_tuples = handle_preprocessed_text(file_type_params)
178
+ elif file_type == 'tokenized':
179
+ text_tuples = handle_tokenized(file_type_params)
180
+ elif file_type == 'tokenized_tagged':
181
+ text_tuples = handle_tokenized_tagged(file_type_params)
182
+
183
+ # the text tuples created from the above processes is passed to the dependency parser
184
+ parsed_text_tuples = parse_text_tuples(text_tuples, parse_model=str(file_type_params.parse_model_path))
185
+ # for text/preprocessed_text, we want to extract the features to place in parsed_text_tuples
186
+ # TODO: check if this step can be skipped by placing features in a step above
187
+ text_feats: List[List[str]] = get_feats_from_text_tuples(text_tuples)
188
+ # place features in FEATS column
189
+ parsed_text_tuples = add_feats(parsed_text_tuples, text_feats)
190
+
191
+ return parsed_text_tuples
192
+
193
+ def get_tagset(parse_model):
194
+ if parse_model == 'catib':
195
+ return 'catib6'
196
+ elif parse_model == 'ud':
197
+ return 'ud'
198
+ else:
199
+ raise f'{parse_model} model does not exist!'
src/dependency_parser/__init__.py ADDED
File without changes
src/dependency_parser/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (166 Bytes). View file
 
src/dependency_parser/__pycache__/biaff_parser.cpython-311.pyc ADDED
Binary file (4.72 kB). View file
 
src/dependency_parser/biaff_parser.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Union, Dict
2
+
3
+ from supar import Parser
4
+ from supar.utils import Dataset
5
+
6
+ from camel_tools.utils.dediac import dediac_ar
7
+
8
+ from ..logger import log
9
+
10
+ """
11
+ conll object from parser
12
+ iterate over sentences from this object
13
+ iterate over the columns per sentence
14
+ create token rows
15
+ create conll sentence of token row tuples
16
+
17
+ """
18
+
19
+ def parser_conll_to_conll_tuples(parser_conll: Dataset) -> List[List[tuple]]:
20
+ conll_sentences = []
21
+ for parser_sentence in parser_conll:
22
+ sentence_tree_token_tuples = []
23
+ for i in range(len(parser_sentence.values[0])):
24
+ token_tuple_row = tuple(column[i] for column in parser_sentence.values)
25
+ sentence_tree_token_tuples.append(token_tuple_row)
26
+ conll_sentences.append(sentence_tree_token_tuples)
27
+ return conll_sentences
28
+
29
+ def filter_tatweel(form):
30
+ if form.replace("_", "").replace("\u0640","").replace("\u005F", "") == "":
31
+ return form
32
+ return form.replace("_", "").replace("\u0640","").replace("\u005F", "")
33
+
34
+ @log
35
+ def parse(conll_path_or_parsed_tuples: Union[List[List[tuple]], str], parse_model:str) -> List[List[tuple]]:
36
+ parser = Parser.load(parse_model)
37
+ return parser.predict(conll_path_or_parsed_tuples, verbose=False, tree=True, proj=True)
38
+
39
+
40
+ def parse_text_tuples(sentence_tuples: List[List[tuple]], parse_model) -> List[List[tuple]]:
41
+ sentence_tuples = [[val[1:4] for val in sent] for sent in sentence_tuples]
42
+ form_lemma_pos_tuple = [[(filter_tatweel(dediac_ar(val[0])), filter_tatweel(dediac_ar(val[1])), val[2]) for val in sent] for sent in sentence_tuples]
43
+ conll = parse(form_lemma_pos_tuple, parse_model=parse_model)
44
+ return parser_conll_to_conll_tuples(conll)
45
+
46
+ def parse_conll(conll_path: str, parse_model) -> List[List[tuple]]:
47
+ conll = parse(conll_path, parse_model=parse_model)
48
+ for i, sent in enumerate(conll):
49
+ conll[i].values[1] = [filter_tatweel(form) for form in sent.values[1]]
50
+ return parser_conll_to_conll_tuples(conll)
src/initialize_disambiguator/__init__.py ADDED
File without changes
src/initialize_disambiguator/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (173 Bytes). View file
 
src/initialize_disambiguator/__pycache__/bert_disambiguator.cpython-311.pyc ADDED
Binary file (583 Bytes). View file
 
src/initialize_disambiguator/__pycache__/disambiguator_interface.cpython-311.pyc ADDED
Binary file (1.7 kB). View file
 
src/initialize_disambiguator/__pycache__/mle_disambiguator.cpython-311.pyc ADDED
Binary file (1.96 kB). View file
 
src/initialize_disambiguator/bert_disambiguator.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ # class BertDisambiguatorAdapter:
4
+ # pass
5
+
6
+ from camel_tools.disambig.bert import BERTUnfactoredDisambiguator
7
+
8
+ def create_bert_disambiguator(analyzer):
9
+ model = BERTUnfactoredDisambiguator.pretrained("msa", top=1000, pretrained_cache=False)
10
+ model._analyzer = analyzer
11
+ return model
src/initialize_disambiguator/disambiguator_interface.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Union
2
+ from camel_tools.morphology.database import MorphologyDB
3
+ from camel_tools.morphology.analyzer import Analyzer
4
+ from camel_tools.disambig.bert import BERTUnfactoredDisambiguator
5
+
6
+ from ..logger import log
7
+ from .bert_disambiguator import create_bert_disambiguator
8
+ from .mle_disambiguator import MLEDisambiguatorAdapter
9
+
10
+ def set_up_analyzer(morphology_db: str) -> Analyzer:
11
+ # used to initialize an Analyzer with ADD_PROP backoff
12
+ # db = MorphologyDB.builtin_db('calima-msa-s31')
13
+ db_type = None if morphology_db == 'r13' else morphology_db
14
+ db = MorphologyDB.builtin_db(db_name=db_type)
15
+ return Analyzer(db=db, backoff='ADD_PROP', cache_size=100000)
16
+
17
+ @log
18
+ def get_disambiguator(model_name: str, morphology_db: str) -> Union[MLEDisambiguatorAdapter, BERTUnfactoredDisambiguator]:
19
+ analyzer = set_up_analyzer(morphology_db)
20
+
21
+ if model_name == 'mle':
22
+ model = MLEDisambiguatorAdapter(analyzer)
23
+ elif model_name == 'bert':
24
+ model = create_bert_disambiguator(analyzer)
25
+ else:
26
+ raise ValueError('Invalid model')
27
+
28
+ return model
src/initialize_disambiguator/mle_disambiguator.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from camel_tools.disambig.mle import MLEDisambiguator
3
+ from camel_tools.disambig.common import DisambiguatedWord
4
+ from camel_tools.morphology.analyzer import Analyzer
5
+
6
+ class MLEDisambiguatorAdapter():
7
+ def __init__(self, analyzer: Analyzer):
8
+ self.disambiguator = MLEDisambiguator(analyzer=analyzer)
9
+
10
+ # def pretrained(self, analyzer):
11
+ # self.disambiguator = self.disambiguator
12
+
13
+ def disambiguate(self, sentence: List[str]) -> List[DisambiguatedWord]:
14
+ return self.disambiguator.disambiguate(sentence)
15
+
16
+ def disambiguate_sentences(self, lines: List[List[str]]) -> List[List[DisambiguatedWord]]:
17
+ return [self.disambiguator.disambiguate(line) for line in lines]
src/logger.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import functools
3
+ import logging
4
+ import os
5
+ import time
6
+ from datetime import datetime
7
+
8
+ logging.basicConfig(level = logging.DEBUG)
9
+ logger = logging.getLogger()
10
+
11
+ log_path = f'{os.getcwd()}/logs.txt'
12
+
13
+ with open(log_path, 'a') as f:
14
+ f.write(f'[{datetime.now().strftime("%d/%b/%Y %H:%M:%S")}]\n')
15
+
16
+ def map_function_to_phrase(function_name):
17
+ """Replace logged function name with a proper phrase.
18
+ """
19
+ if function_name == "parse":
20
+ return "parsing duration"
21
+ elif function_name == "get_disambiguator":
22
+ return "disambiguator setup duration"
23
+ elif function_name == "disambiguate_sentences":
24
+ return "disambiguating sentences"
25
+ elif function_name == "main":
26
+ return "total execution time"
27
+ else:
28
+ return function_name
29
+
30
+ def log(func):
31
+ @functools.wraps(func)
32
+ def wrapper(*args, **kwargs):
33
+ try:
34
+
35
+ start_time = time.time()
36
+ result = func(*args, **kwargs)
37
+ end_time = time.time()
38
+
39
+ with open(log_path, 'a') as f:
40
+ f.write(f'{map_function_to_phrase(func.__name__)}: {round(end_time - start_time, 2)}s\n')
41
+ return result
42
+ except Exception as e:
43
+ logger.exception(f"Exception raised in {func.__name__}. exception: {str(e)}")
44
+ raise e
45
+
46
+ return wrapper
src/parse_disambiguation/__init__.py ADDED
File without changes
src/parse_disambiguation/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (169 Bytes). View file
 
src/parse_disambiguation/__pycache__/disambiguation_analysis.cpython-311.pyc ADDED
Binary file (4.99 kB). View file
 
src/parse_disambiguation/__pycache__/feature_extraction.cpython-311.pyc ADDED
Binary file (14.1 kB). View file
 
src/parse_disambiguation/disambiguation_analysis.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from typing import List
3
+ from camel_tools.disambig.common import DisambiguatedWord
4
+ from camel_tools.utils.dediac import dediac_ar
5
+ from ..classes import Token
6
+
7
+ def get_tok_match_analysis(disambig_word, token):
8
+ for i, scored_analysis in enumerate(disambig_word.analyses):
9
+ if dediac_ar(scored_analysis.analysis['diac']) == dediac_ar(token):
10
+ if i != 0:
11
+ # print(f"#NOT_FIRST Input token: {token}, analysis: {get_first_analysis(disambig_word)['diac']}", file=sys.stderr)
12
+ print(f"#NOT_FIRST {token}", file=sys.stderr)
13
+
14
+ return scored_analysis.analysis
15
+
16
+ # print(f"#NOAN token {token}, analysis: {get_first_analysis(disambig_word)['diac']}", file=sys.stderr)
17
+ print(f"#NOAN {token}", file=sys.stderr)
18
+
19
+ return get_first_analysis(disambig_word) # no token match, so just return first
20
+
21
+
22
+ def get_analysis_by_criteria(disambig_word: DisambiguatedWord, selection_criteria: dict) -> dict:
23
+ # runs assertions to ensure the data is good
24
+ is_analysis(disambig_word)
25
+
26
+ for scored_analysis in disambig_word.analyses:
27
+ analysis = scored_analysis.analysis
28
+ if all(analysis[key] == selection_criteria[key] for key in selection_criteria):
29
+ return analysis
30
+
31
+ # if the criteria is not found, return the first analysis
32
+ print(f"No analysis found for the given criteria on {disambig_word.word}, will return the first analysis.")
33
+ return get_first_analysis(disambig_word)
34
+
35
+ def is_analysis(disambig_word: DisambiguatedWord) -> dict:
36
+ # this should not be empty
37
+ assert disambig_word, "Disambiguated word not found!"
38
+
39
+ # this could be empty if there is no backoff, and this code assumes backoff
40
+ assert disambig_word.analyses, "No analyses found, are you using backoff?"
41
+ assert disambig_word.analyses[0], "No analyses found, are you using backoff?"
42
+
43
+ def get_first_analysis(disambig_word: DisambiguatedWord):
44
+ # runs assertions to ensure the data is good
45
+ is_analysis(disambig_word)
46
+
47
+ return disambig_word.analyses[0].analysis
48
+
49
+
50
+ def get_sentence_analysis(disambiguated_sentence: List[DisambiguatedWord], token_line, selection, selection_criteria) -> List[dict]:
51
+ # sourcery skip: switch
52
+ # get an analysis based on the selection criteria
53
+ # top means take first
54
+ # tok_match takes the first analysis that matches diac, otherwise takes first
55
+ # match means take the first analysis that matches the selection criteria.
56
+ # selection criteria: {'criteria1': 'value1', 'criteria2': 'value2'...}
57
+ # return None if criteria not fulfilled.
58
+ # TODO: check if selection criteria contains valid keys
59
+ if selection == 'top':
60
+ return [get_first_analysis(disambig_word) for disambig_word in disambiguated_sentence]
61
+ elif selection == 'tok_match':
62
+ return [get_tok_match_analysis(disambig_word, token) for disambig_word, token in zip(disambiguated_sentence, token_line)]
63
+ elif selection == 'match':
64
+ return [get_analysis_by_criteria(disambig_word, selection_criteria) for disambig_word in disambiguated_sentence]
65
+ else:
66
+ raise ValueError(f"the selection {selection} is not valid!")
67
+
68
+ def to_sentence_analysis_list(disambiguated_sentences: List[List[DisambiguatedWord]], token_lines, selection: str='tok_match', selection_criteria: dict=None) -> List[List[Token]]:
69
+ return [get_sentence_analysis(disambiguated_sentence, token_line, selection, selection_criteria) for disambiguated_sentence, token_line in zip(disambiguated_sentences, token_lines)]
src/parse_disambiguation/feature_extraction.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Given a list of list of analyses (one analysis per token),
2
+ get features based on a given criteria.
3
+
4
+ If no criteria is given, return atbtok and catib6
5
+ """
6
+
7
+ import re
8
+ from typing import List
9
+ import json
10
+ from camel_tools.utils.dediac import dediac_ar
11
+ from camel_tools.utils.charmap import CharMapper
12
+ from camel_tools.utils.transliterate import Transliterator
13
+ import pandas as pd
14
+
15
+ FEATURES_LIST = ["pos", "prc3", "prc2", "prc1", "prc0", "enc0", "asp", "vox", "mod", "gen", "num", "stt", "cas", "per", "rat"]
16
+
17
+ def feats_dict_to_string(feats_dict):
18
+ # prc3=na|prc2=na|prc1=na|prc0=na|per=na|asp=na|vox=na|mod=na|gen=na|num=na|stt=na|cas=na|enc0=na|rat=na
19
+ feats_str = json.dumps(feats_dict)
20
+ return feats_str.replace('", "', "|").replace('": "', "=").replace('{"', '').replace('"}', '')
21
+
22
+ def build_clitic_feats_dict(clitic_feat_list):
23
+ assert len(clitic_feat_list) != 0, f'invalid clitic, {clitic_feat_list}'
24
+ assert len(clitic_feat_list) == 1, f'multiple clitics matched when only one should, {clitic_feat_list}'
25
+ clitic_feat_list[0]['deciding_feat']
26
+ final_clitic_feats = {k: v for k,v in clitic_feat_list[0].items() if k in FEATURES_LIST}
27
+ final_clitic_feats['token_type'] = clitic_feat_list[0]['deciding_feat'].split(':')[0]
28
+ return final_clitic_feats
29
+
30
+ def get_clitic_feats(token, clitic_order, clitic_feats, stem_feats):
31
+ mapper = CharMapper.builtin_mapper('ar2bw')
32
+ transliterator = Transliterator(mapper)
33
+ token = transliterator.transliterate(dediac_ar(token))
34
+ filtered_clitics = clitic_feats[(clitic_feats.clitic == token) & (clitic_feats.deciding_feat.str.startswith(clitic_order))]
35
+ clitic_list = [f'{k}:{v}' for k, v in stem_feats.items() if k.startswith(clitic_order) and v not in ['0', 'na']]
36
+
37
+ for feat_check in clitic_list:
38
+ clitic_feat_list = filtered_clitics[filtered_clitics.deciding_feat == feat_check].to_dict('records')
39
+ if clitic_feat_list:
40
+ return build_clitic_feats_dict(clitic_feat_list)
41
+ assert False, f"clitic '{token}' does not exist in clitics list. Stem features: {stem_feats}"
42
+
43
+ def get_stem_feats(word_analysis):
44
+ return {feat: word_analysis[feat] for feat in FEATURES_LIST}
45
+
46
+ def get_clitic_order(token):
47
+ if token.endswith('+'):
48
+ clitic_order = 'prc'
49
+ elif token.startswith('+'):
50
+ clitic_order = 'enc'
51
+ return clitic_order
52
+
53
+ def is_clitic(token):
54
+ return (token.startswith('+') or token.endswith('+')) and not re.match(r'^\++$', token)
55
+
56
+ def empty_clitic_feats_from_baseword(stem_feats):
57
+ clitic_type_list = ["prc3", "prc2", "prc1", "prc0", "enc0"]
58
+ for clitic_type in clitic_type_list:
59
+ if stem_feats[clitic_type] not in ['0', 'na', 'Al_det']:
60
+ stem_feats[clitic_type] = '0'
61
+ return stem_feats
62
+
63
+
64
+ def r13_fixes(token, stem_feats):
65
+ # handling edge cases where li comes from variations of wa li>n (i.e. li>nhu, li>nanY)
66
+ li_feats = {'pos': 'conj_sub', 'prc3': '0', 'prc2': 'wa_conj', 'prc1': '0', 'prc0': 'na', 'enc0': '3ms_pron', 'asp': 'na', 'vox': 'na', 'mod': 'na', 'gen': 'na', 'num': 'na', 'stt': 'na', 'cas': 'na', 'per': 'na', 'rat': 'na'}
67
+ if {k for k, _ in stem_feats.items() ^ li_feats.items()} == {'enc0'}:
68
+ stem_feats['prc1'] = 'li_conj'
69
+
70
+ # handling edge cases where li comes from variations of li>n (i.e. li>nhu, li>nanY)
71
+ li_feats = {'pos': 'conj_sub', 'prc3': '0', 'prc2': '0', 'prc1': '0', 'prc0': 'na', 'enc0': '0', 'asp': 'na', 'vox': 'na', 'mod': 'na', 'gen': 'na', 'num': 'na', 'stt': 'na', 'cas': 'na', 'per': 'na', 'rat': 'na'}
72
+ if {k for k, _ in stem_feats.items() ^ li_feats.items()} == {'enc0'}:
73
+ stem_feats['prc1'] = 'li_conj'
74
+
75
+ # handling edge cases where li comes from li>n
76
+ if token == 'لِ+' and stem_feats == {'pos': 'conj_sub', 'prc3': '0', 'prc2': '0', 'prc1': '0', 'prc0': 'na', 'enc0': '0', 'asp': 'na', 'vox': 'na', 'mod': 'na', 'gen': 'na', 'num': 'na', 'stt': 'na', 'cas': 'na', 'per': 'na', 'rat': 'na'}:
77
+ stem_feats['prc1'] = 'li_conj'
78
+
79
+ # handling edge cases where li comes from wa li>n
80
+ if token == 'لِ+' and stem_feats == {'pos': 'conj_sub', 'prc3': '0', 'prc2': 'wa_conj', 'prc1': '0', 'prc0': 'na', 'enc0': '3ms_pron', 'asp': 'na', 'vox': 'na', 'mod': 'na', 'gen': 'na', 'num': 'na', 'stt': 'na', 'cas': 'na', 'per': 'na', 'rat': 'na'}:
81
+ stem_feats['prc1'] = 'li_conj'
82
+
83
+ # handling edge cases where mA comes from qlmA
84
+ if token == '+ما' and stem_feats == {'pos': 'conj', 'prc3': '0', 'prc2': '0', 'prc1': '0', 'prc0': 'na', 'enc0': '0', 'asp': 'na', 'vox': 'na', 'mod': 'na', 'gen': 'na', 'num': 'na', 'stt': 'na', 'cas': 'na', 'per': 'na', 'rat': 'n'}:
85
+ stem_feats['enc0'] = 'mA_sub'
86
+
87
+ # handling an edge case where li comes from likY
88
+ # added li_conj to clitic_feats.csv
89
+ if token == 'لِ+' and stem_feats == {'pos': 'conj', 'prc3': '0', 'prc2': '0', 'prc1': '0', 'prc0': 'na', 'enc0': '0', 'asp': 'na', 'vox': 'na', 'mod': 'na', 'gen': 'na', 'num': 'na', 'stt': 'na', 'cas': 'na', 'per': 'na', 'rat': 'na'}:
90
+ stem_feats['prc1'] = 'li_conj'
91
+
92
+ def add_remaining_features(tokens_df, stem_feats, clitic_feats):
93
+ existing_clitics = ['prc0']
94
+
95
+ clitic_feats_list = []
96
+
97
+ for _, row in tokens_df.iterrows():
98
+ token = row['token']
99
+ if not is_clitic(token):
100
+ baseword_feats_dict = empty_clitic_feats_from_baseword(dict(stem_feats))
101
+ baseword_feats_dict['token_type'] = 'baseword'
102
+ clitic_feats_list.append(baseword_feats_dict)
103
+ else:
104
+ clitic_order = get_clitic_order(token)
105
+ # handling an edge case where lA is negative
106
+ if token == 'لِ+' and \
107
+ (stem_feats == {'pos': 'conj_sub', 'prc3': '0', 'prc2': '0', 'prc1': '0', 'prc0': 'na', 'enc0': 'lA_neg', 'asp': 'na', 'vox': 'na', 'mod': 'na', 'gen': 'na', 'num': 'na', 'stt': 'na', 'cas': 'na', 'per': 'na', 'rat': 'na'}
108
+ or stem_feats == {'pos': 'conj_sub', 'prc3': '0', 'prc2': 'fa_conj', 'prc1': '0', 'prc0': 'na', 'enc0': 'lA_neg', 'asp': 'na', 'vox': 'na', 'mod': 'na', 'gen': 'na', 'num': 'na', 'stt': 'na', 'cas': 'na', 'per': 'na', 'rat': 'na'}
109
+ or stem_feats == {'pos': 'conj_sub', 'prc3': '0', 'prc2': 'wa_part', 'prc1': '0', 'prc0': 'na', 'enc0': 'lA_neg', 'asp': 'na', 'vox': 'na', 'mod': 'na', 'gen': 'na', 'num': 'na', 'stt': 'na', 'cas': 'na', 'per': 'na', 'rat': 'na'}
110
+ ):
111
+ stem_feats['prc1'] = 'li_prep'
112
+
113
+ r13_fixes(token, stem_feats)
114
+
115
+ clitic_feats_list.append(get_clitic_feats(token.replace('+', ''), clitic_order, clitic_feats, stem_feats))
116
+
117
+ feats_df = pd.DataFrame(clitic_feats_list)
118
+ assert tokens_df.shape[0] == feats_df.shape[0], f'token-feature mismatch!,\ntokens: \n{tokens_df},\n\n features: \n{feats_df}'
119
+
120
+ return pd.concat([tokens_df, feats_df], axis=1)
121
+
122
+ def get_lemmas(lemma, tokens):
123
+ lemmas = []
124
+ for token in tokens:
125
+ if '+' in token:
126
+ lemmas.append(token)
127
+ else:
128
+ lemmas.append(lemma)
129
+ return lemmas
130
+
131
+ def get_main_features_df(word_analysis):
132
+ # if there are no clitics
133
+ if '+' not in word_analysis['catib6']:
134
+ tokens = [word_analysis['atbtok']]
135
+ catib6 = [word_analysis['catib6']]
136
+ ud = [word_analysis['ud']]
137
+ lemmas = [word_analysis['lex']]
138
+ else:
139
+ tokens = word_analysis['atbtok'].split('_')
140
+ catib6 = word_analysis['catib6'].split('+')
141
+ ud = word_analysis['ud'].split('+')
142
+ lemmas = get_lemmas(word_analysis['lex'], tokens)
143
+
144
+ if len(catib6) < len(tokens):
145
+ print(tokens)
146
+ print(catib6)
147
+ catib6.append("NOM")
148
+ ud.append("NOUN")
149
+ return pd.DataFrame({'token': tokens, 'catib6': catib6, 'ud': ud, 'lemma': lemmas})
150
+ elif len(tokens) < len(catib6):
151
+ return pd.DataFrame({'token': tokens, 'catib6': catib6[:len(tokens)], 'ud': ud[:len(tokens)], 'lemma': lemmas})
152
+
153
+ return pd.DataFrame({'token': tokens, 'catib6': catib6, 'ud': ud, 'lemma': lemmas})
154
+
155
+ def get_word_features_df(word_analysis, clitic_feats):
156
+ """if a word is composed of multiple tokens, return them all.
157
+ otherwise, just return the word and catib6 tag
158
+
159
+ Args:
160
+ word_analysis (dict): analysis generated from a cameltools disambiguator
161
+
162
+ Returns:
163
+ List[tuple]: a list of one or more tokens
164
+ """
165
+ # gets forms, tokens, feats, ...
166
+ main_feats = get_main_features_df(word_analysis)
167
+ stem_feats = get_stem_feats(word_analysis)
168
+ return add_remaining_features(main_feats, stem_feats, clitic_feats)
169
+
170
+ def join_feats(word_feats_df, tagset):
171
+ word_features = {
172
+ 'tokens': list(word_feats_df['token']),
173
+ 'pos_tags': list(word_feats_df[tagset]),
174
+ 'lemmas': list(word_feats_df['lemma']),
175
+ }
176
+
177
+ word_feats_df.drop(['token', tagset, 'lemma'], axis=1, inplace=True)
178
+ feats = word_feats_df.to_dict('records')
179
+ word_features['feats'] = [feats_dict_to_string(row) for row in feats]
180
+
181
+ return word_features
182
+
183
+ def update_sentence_features(sentence_features, word_features):
184
+ sentence_features['tokens'] += word_features['tokens']
185
+ sentence_features['lemmas'] += word_features['lemmas']
186
+ sentence_features['pos_tags'] += word_features['pos_tags']
187
+ sentence_features['feats'] += word_features['feats']
188
+
189
+ return sentence_features
190
+
191
+ def build_token_list(sentence_features):
192
+ return [
193
+ (idx, dediac_ar(token), lemma, pos_tag, '_', feats, '_', '_', '_', '_')
194
+ for idx, (token, lemma, pos_tag, feats) in enumerate(
195
+ zip(sentence_features['tokens'], sentence_features['lemmas'], sentence_features['pos_tags'], sentence_features['feats'])
196
+ , 1)
197
+ ]
198
+
199
+ def to_conll_fields_list(sentence_analysis_list: List[List[dict]], clitic_feats, tagset):
200
+ sentence_features_list = []
201
+
202
+ for sentence_analysis in sentence_analysis_list:
203
+ sentence_features = {'tokens': [], 'lemmas': [], 'pos_tags': [], 'feats': []}
204
+ for word_analysis in sentence_analysis:
205
+ word_features_df = get_word_features_df(word_analysis, clitic_feats)
206
+
207
+ word_features = join_feats(word_features_df, tagset)
208
+ sentence_features = update_sentence_features(sentence_features, word_features)
209
+ token_list = build_token_list(sentence_features)
210
+ sentence_features_list.append(token_list)
211
+
212
+ return sentence_features_list
src/utils/__init__.py ADDED
File without changes
src/utils/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (154 Bytes). View file
 
src/utils/__pycache__/conll_fixes.cpython-311.pyc ADDED
Binary file (2.29 kB). View file
 
src/utils/__pycache__/model_downloader.cpython-311.pyc ADDED
Binary file (2.15 kB). View file
 
src/utils/__pycache__/text_cleaner.cpython-311.pyc ADDED
Binary file (2.56 kB). View file
 
src/utils/conll_fixes.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The parsing code requires that conll files have two newlines at the end.
2
+ The functions below ensure that this is the case before parsing"""
3
+
4
+ def read_n_to_last_line(filename, n = 1):
5
+ """Returns the nth before last line of a file (n=1 gives last line)
6
+
7
+ from https://stackoverflow.com/questions/46258499/how-to-read-the-last-line-of-a-file-in-python"""
8
+ num_newlines = 0
9
+ with open(filename, 'rb') as f:
10
+ try:
11
+ for i in range(1, n+1):
12
+ f.seek(-i, 2)
13
+ # while num_newlines < n:
14
+ # f.seek(-2, os.SEEK_CUR)
15
+ # if f.read(1) == b'\n':
16
+ # num_newlines += 1
17
+ except OSError:
18
+ f.seek(0)
19
+ last_line = f.readline().decode()
20
+ return last_line
21
+
22
+ def add_newlines(file_path, second_last_line, last_line):
23
+ with open(file_path, 'a') as f:
24
+ if second_last_line != '\n':
25
+ f.write('\n')
26
+ if last_line != '\n':
27
+ f.write('\n')
28
+
29
+ def adjust_eof_newlines(file_path):
30
+ second_last_line = read_n_to_last_line(file_path, 2)
31
+ last_line = read_n_to_last_line(file_path, 1)
32
+ if not (second_last_line == last_line == '\n'):
33
+ add_newlines(file_path, second_last_line, last_line)
src/utils/model_downloader.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ Script used to set up different parts of the text_to_conll_cli.py"""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from huggingface_hub import hf_hub_download
6
+
7
+ def download_default_models(model_path: Path) -> None:
8
+ # check if models folder exists
9
+ if not os.path.exists(model_path):
10
+ os.mkdir(model_path)
11
+
12
+ # check if default models exist
13
+ if not os.path.exists(model_path / "CAMeLBERT-CATiB-biaffine.model"):
14
+ print('downloading catib model')
15
+ hf_hub_download(repo_id="CAMeL-Lab/camelbert-catib-parser", filename="CAMeLBERT-CATiB-biaffine.model", local_dir=model_path)
16
+ if not os.path.exists(model_path / "CAMeLBERT-UD-biaffine.model"):
17
+ print('downloading ud model')
18
+ hf_hub_download(repo_id="CAMeL-Lab/camelbert-ud-parser", filename="CAMeLBERT-UD-biaffine.model", local_dir=model_path)
19
+ print('Default models downloaded.')
20
+
21
+ def get_model_name(parse_model: str, model_path: Path) -> str:
22
+ if parse_model == "catib":
23
+ if os.path.exists(model_path / "CAMeLBERT-CATiB-biaffine.model"):
24
+ return "CAMeLBERT-CATiB-biaffine.model"
25
+ else:
26
+ assert False, "CATiB default model not found. Did you run 'python download_models'?"
27
+ elif parse_model == "ud":
28
+ if os.path.exists(model_path / "CAMeLBERT-UD-biaffine.model"):
29
+ return "CAMeLBERT-UD-biaffine.model"
30
+ else:
31
+ assert False, "UD default model not found. Did you run 'python download_models'?"
32
+ else:
33
+ return parse_model
src/utils/text_cleaner.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from camel_tools.tokenizers.word import simple_word_tokenize
2
+ from camel_tools.utils.dediac import dediac_ar
3
+ from camel_tools.utils.normalize import normalize_unicode
4
+ from camel_tools.utils.charmap import CharMapper
5
+
6
+ def clean_line(line, arclean):
7
+ return simple_word_tokenize(arclean(dediac_ar(normalize_unicode(line.strip()))))
8
+
9
+ def split_lines_words(lines):
10
+ return [line.strip().split() for line in lines]
11
+
12
+ def clean_mad(lines):
13
+ new_lines = []
14
+ for line in lines:
15
+ new_line = []
16
+ for word in line:
17
+ # replacing underscore with mad character
18
+ if word == 'ـ':
19
+ new_line.append('_')
20
+ else:
21
+ new_line.append(word)
22
+ new_lines.append(new_line)
23
+ return new_lines
24
+
25
+ def clean_lines(lines, arclean):
26
+ return [clean_line(line, arclean) for line in lines]
27
+
28
+ if __name__ == '__main__':
29
+ lines = []
30
+ with open('data/sample_text.txt', 'r') as f:
31
+ lines = f.readlines()
32
+
33
+ arclean = CharMapper.builtin_mapper("arclean")
34
+ new_lines = clean_lines(lines, arclean)
tests/test_feats.tsv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ token catib6 ud lemma prc3 prc2 prc1 prc0 per asp vox mod gen num stt cas enc0 rat
2
+ بِ+ PRT ADP بِ+ 0 0 0 na na na na na na na na na 0 na
3
+ اِسْمِ NOM NOUN ٱِسْم 0 0 bi_prep 0 na na na na m s c g 0 i
tests/test_feature_extraction.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ from pandas import read_csv
3
+
4
+ from src.parse_disambiguation.feature_extraction import get_word_features_df, join_feats
5
+
6
+ @pytest.fixture
7
+ def word_analysis():
8
+ return {'diac': 'بِسْمِ', 'lex': 'ٱِسْم', 'caphi': 'b_i_s_m_i',
9
+ 'gloss': 'in/by_+_(the)_Name_of_+_[def.gen.]',
10
+ 'bw': 'بِ/PREP+ٱِسْم/NOUN+ِ/CASE_DEF_GEN', 'pos': 'noun',
11
+ 'catib6': 'PRT+NOM', 'ud': 'ADP+NOUN', 'root': 'س.م.#',
12
+ 'pattern': 'بِ1ْ2ِ', 'prc3': '0', 'prc2': '0', 'prc1':
13
+ 'bi_prep', 'prc0': '0', 'per': 'na', 'asp': 'na',
14
+ 'vox': 'na', 'mod': 'na', 'form_gen': 'm', 'gen': 'm',
15
+ 'form_num': 's', 'num': 's', 'stt': 'c', 'cas': 'g',
16
+ 'enc0': '0', 'rat': 'i', 'source': 'lex', 'd1seg': 'بِسْمِ',
17
+ 'd2seg': 'بِ+_سْمِ', 'd3seg': 'بِ+_سْمِ', 'atbseg': 'بِ+_سْمِ',
18
+ 'd1tok': 'بِسْمِ', 'd2tok': 'بِ+_اِسْمِ', 'd3tok': 'بِ+_اِسْمِ',
19
+ 'atbtok': 'بِ+_اِسْمِ', 'bwtok': 'بِ+_ٱِسْم_+ِ', 'pos_logprob': -0.4344233,
20
+ 'lex_logprob': -3.156274, 'pos_lex_logprob': -3.156274,
21
+ 'stem': 'بِسْمِ', 'stemgloss': 'in/by_+_(the)_Name_of_+_[def.gen.]', 'stemcat': 'FW-Wa'
22
+ }
23
+
24
+ @pytest.fixture
25
+ def clitic_feats():
26
+ return read_csv('data/clitic_feats.csv')
27
+
28
+ @pytest.fixture
29
+ def word_feats():
30
+ df = read_csv('tests/test_feats.tsv', sep='\t')
31
+ return df.astype(str).astype(object)
32
+
33
+
34
+ def test_get_word_features_df(word_analysis, clitic_feats, word_feats):
35
+ tagset = "catib6"
36
+
37
+ df = get_word_features_df(word_analysis, clitic_feats)
38
+ df = df.astype(str).astype(object)
39
+ assert word_feats.equals(df)
40
+
41
+ def test_join_feats_catib6(word_feats):
42
+ word_feats = join_feats(word_feats, 'catib6')
43
+
44
+ assert word_feats['tokens'] == ['بِ+', 'اِسْمِ']
45
+ assert word_feats['pos_tags'] == ['PRT', 'NOM']
46
+ assert word_feats['lemmas'] == ['بِ+', 'ٱِسْم']
47
+ assert word_feats['feats'] == ['ud=ADP|prc3=0|prc2=0|prc1=0|prc0=na|per=na|asp=na|vox=na|mod=na|gen=na|num=na|stt=na|cas=na|enc0=0|rat=na', 'ud=NOUN|prc3=0|prc2=0|prc1=bi_prep|prc0=0|per=na|asp=na|vox=na|mod=na|gen=m|num=s|stt=c|cas=g|enc0=0|rat=i']
48
+
49
+ def test_join_feats_ud(word_feats):
50
+ word_feats = join_feats(word_feats, 'ud')
51
+
52
+ assert word_feats['tokens'] == ['بِ+', 'اِسْمِ']
53
+ assert word_feats['pos_tags'] == ['ADP', 'NOUN']
54
+ assert word_feats['lemmas'] == ['بِ+', 'ٱِسْم']
55
+ assert word_feats['feats'] == ['catib6=PRT|prc3=0|prc2=0|prc1=0|prc0=na|per=na|asp=na|vox=na|mod=na|gen=na|num=na|stt=na|cas=na|enc0=0|rat=na', 'catib6=NOM|prc3=0|prc2=0|prc1=bi_prep|prc0=0|per=na|asp=na|vox=na|mod=na|gen=m|num=s|stt=c|cas=g|enc0=0|rat=i']
text_to_conll_cli.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Disambiguator and Conll builder CLI.
3
+
4
+ Usage:
5
+ text_to_conll_cli (-i <input> | --input=<input> | -s <string> | --string=<string>)
6
+ (-f <file_type> | --file_type=<file_type>)
7
+ [-b <morphology_db_type> | --morphology_db_type=<morphology_db_type>]
8
+ [-d <disambiguator> | --disambiguator=<disambiguator>]
9
+ [-m <model> | --model=<model>]
10
+ text_to_conll_cli (-h | --help)
11
+
12
+ Options:
13
+ -i <input> --input=<input>
14
+ A text file or conll file.
15
+ -s <string> --string=<string>
16
+ A string to parse.
17
+ -f <file_type> --file_type=<file_type>
18
+ The type of file passed. Could be
19
+ conll: conll
20
+ text: raw text
21
+ preprocessed_text: whitespace tokenized text (text will not be cleaned)
22
+ tokenized_tagged: text is already tokenized and POS tagged, in tuple form
23
+ tokenized: text is already tokenized, only parse tokenized input; don't disambiguate to add POS tags or features
24
+ -b <morphology_db_type> --morphology_db_type=<morphology_db_type>
25
+ The morphology database to use; will use camel_tools built-in by default [default: r13]
26
+ -d <disambiguator> --disambiguator=<disambiguator>
27
+ The disambiguation technique used to tokenize the text lines, either 'mle' or 'bert' [default: bert]
28
+ -m <model> --model=<model>
29
+ The name BERT model used to parse (to be placed in the model directory) [default: catib]
30
+ -h --help
31
+ Show this screen.
32
+ """
33
+
34
+ from src.logger import log
35
+ from pathlib import Path
36
+ from camel_tools.utils.charmap import CharMapper
37
+ from src.conll_output import print_to_conll, text_tuples_to_string
38
+ from src.data_preparation import get_file_type_params, get_tagset, parse_text
39
+ from src.utils.model_downloader import get_model_name
40
+ from docopt import docopt
41
+ from transformers.utils import logging
42
+ from pandas import read_csv
43
+
44
+ arguments = docopt(__doc__)
45
+
46
+ logging.set_verbosity_error()
47
+
48
+ def get_file_type(file_type):
49
+ if file_type in ['conll', 'text', 'preprocessed_text', 'tokenized_tagged', 'tokenized']:
50
+ return file_type
51
+ assert False, 'Unknown file type'
52
+
53
+ @log
54
+ def main():
55
+ root_dir = Path(__file__).parent
56
+ model_path = root_dir/"models"
57
+
58
+ # camel_tools import used to clean text
59
+ arclean = CharMapper.builtin_mapper("arclean")
60
+
61
+ #
62
+ ### Get clitic features
63
+ #
64
+ clitic_feats_df = read_csv(root_dir / 'data/clitic_feats.csv')
65
+ clitic_feats_df = clitic_feats_df.astype(str).astype(object) # so ints read are treated as string objects
66
+
67
+
68
+ #
69
+ ### cli user input ###
70
+ #
71
+ file_path = arguments['--input']
72
+ string_text = arguments['--string']
73
+ file_type = get_file_type(arguments['--file_type'])
74
+ morphology_db_type = arguments['--morphology_db_type']
75
+ disambiguator_type = arguments['--disambiguator']
76
+ parse_model = arguments['--model']
77
+
78
+
79
+ #
80
+ ### Set up parsing model
81
+ # (download defaults models, and get correct model name from the models directory)
82
+ #
83
+ model_name = get_model_name(parse_model, model_path=model_path)
84
+
85
+ #
86
+ ### get tagset (depends on model)
87
+ #
88
+ tagset = get_tagset(parse_model)
89
+
90
+
91
+ #
92
+ ### main code ###
93
+ #
94
+ lines = []
95
+ if string_text is not None:
96
+ lines = [string_text]
97
+ elif file_path is not None:
98
+ with open(file_path, 'r') as f:
99
+ lines = [line for line in f.readlines() if line.strip()]
100
+
101
+
102
+ file_type_params = get_file_type_params(lines, file_type, file_path, model_path/model_name,
103
+ arclean, disambiguator_type, clitic_feats_df, tagset, morphology_db_type)
104
+ parsed_text_tuples = parse_text(file_type, file_type_params)
105
+
106
+ string_lines = text_tuples_to_string(parsed_text_tuples, file_type, sentences=lines)
107
+ print_to_conll(string_lines)
108
+
109
+ if __name__ == '__main__':
110
+ main()