|
1 | 1 | # pilsner |
2 | | -Utility for dictionary-based named entity recognition |
| 2 | + |
| 3 | +Python implemented library servicing named entity recognition |
| 4 | + |
| 5 | +[![pypi][pypi-img]][pypi-url] |
| 6 | + |
| 7 | +[pypi-img]: https://img.shields.io/pypi/v/pilsner?style=plastic |
| 8 | +[pypi-url]: https://pypi.org/project/pilsner/ |
| 9 | + |
| 10 | +## 1. Purpose |
| 11 | + |
| 12 | +This library is Python implementation of toolkit for dictionary based named |
| 13 | +entity recognition. It is intended to store any thesaurus in a trie-like |
| 14 | +structure and identify any of stored synonyms in a string. |
| 15 | + |
| 16 | +## 2. Installation and dependencies |
| 17 | + |
| 18 | +```bash |
| 19 | +pip install pilsner |
| 20 | +``` |
| 21 | + |
| 22 | +`pilsner` is tested in Python 3.6, 3.7, and 3.8. |
| 23 | + |
| 24 | +The only dependency is `sic` package. While it can be automatically installed |
| 25 | +at the time of `pilsner` installation, manual installation of `sic` beforehand |
| 26 | +might also be considered (see benchmark of cythonized vs pure Python |
| 27 | +implementation in `sic` docimentation, |
| 28 | +[https://pypi.org/project/sic/](https://pypi.org/project/sic/)). |
| 29 | + |
| 30 | +## 3. Diagram |
| 31 | + |
| 32 | +`pilsner` consists of two major components: `Model` and `Utility`. `Model` |
| 33 | +class provides storage for the dictionary and string normalization rules, as |
| 34 | +well as low-level methods for populating this storage. `Utility` class provides |
| 35 | +high-level methods for storing and retrieving data to/from `Model` instance. |
| 36 | + |
| 37 | + |
| 38 | + |
| 39 | +## 4. Usage |
| 40 | + |
| 41 | +```python |
| 42 | +import pilsner |
| 43 | +``` |
| 44 | + |
| 45 | +### 4.1. Initialize model |
| 46 | + |
| 47 | +- To initialize empty model: |
| 48 | + |
| 49 | +```python |
| 50 | +m = pilsner.Model() |
| 51 | +``` |
| 52 | + |
| 53 | +- To specify path to temporary database for empty model: |
| 54 | + |
| 55 | +```python |
| 56 | +m = pilsner.Model(storage_location='path/to/database.file') |
| 57 | +``` |
| 58 | + |
| 59 | +- To create empty model that uses database created in memory rather than on |
| 60 | +disk: |
| 61 | + |
| 62 | +```python |
| 63 | +m = pilsner.Model(storage_location=':memory:') |
| 64 | +``` |
| 65 | + |
| 66 | +> If database is created in memory, the model cannot be later saved on disk |
| 67 | +(can only be used instantly). |
| 68 | + |
| 69 | +- To load model from disk: |
| 70 | + |
| 71 | +```python |
| 72 | +m = pilsner.Model(filename='path/to/model') |
| 73 | +``` |
| 74 | + |
| 75 | +> More on how model is saved to and loaded from disk - see |
| 76 | +[4.6. Save model](#46-save-model) and [4.7. Load model](#47-load-model). |
| 77 | + |
| 78 | +### 4.2. Add string normalization units |
| 79 | + |
| 80 | +- Depending on the dictionary and nature of the text supposed to be parsed, |
| 81 | +string normalization might not be required at all, and nothing specific is to |
| 82 | +be done here in such case. |
| 83 | +- Without string normalization, synonyms from the dictionary will be stored as |
| 84 | +they are and looked up by recognizer case-sensitively. |
| 85 | +- To add a single normalization unit: |
| 86 | + |
| 87 | +```python |
| 88 | +# Assuming m is pilsner.Model instance: |
| 89 | +m.add_normalizer( |
| 90 | + normalizer_name='normalizer_tag', |
| 91 | + filename='path/to/normalizer_config.xml' |
| 92 | +) |
| 93 | +``` |
| 94 | + |
| 95 | +> String normalization is technically done by `sic` component. See |
| 96 | +> documentation for `sic` at |
| 97 | +> [https://pypi.org/project/sic/](https://pypi.org/project/sic/) to learn how |
| 98 | +> to design normalizer config. |
| 99 | +
|
| 100 | +- Model can embed more than one normalization unit. |
| 101 | +- Default normalization unit for the model is the one added first or the last |
| 102 | +one added with parameter `default` set to `True`. |
| 103 | +- Having multiple normalization units in one model makes perfect sense when the |
| 104 | +stored dictionary contains synonyms of different nature that should be |
| 105 | +normalized in different ways (for example, abbreviations probably should not |
| 106 | +get normalized at all, while other synonyms might include tokens or punctuation |
| 107 | +marks that should not affect entity recognition). For that purpose, Model class |
| 108 | +includes `normalizer_map` dict that is supposed to map names of added |
| 109 | +normalization units to values in specific field in a dictionary designating the |
| 110 | +way a synonym should be normalized (tokenizer field, or tokenizer column): |
| 111 | + |
| 112 | +```python |
| 113 | +# Assuming m is pilsner.Model instance: |
| 114 | +m.normalizer_map = { |
| 115 | + 'synonym_type_1': 'normalizer_1', |
| 116 | + 'synonym_type_2': 'normalizer_2' |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +> The snippet above instructs `pilsner` to normalize synonyms that have |
| 121 | +> `synonym_type_1` value in `tokenizer` column with `normalizer_1` |
| 122 | +> normalization unit, and normalize synonyms that have `synonym_type_2` value |
| 123 | +> in `tokenizer` column with `normalizer_2` normalization unit. For more about |
| 124 | +> fields in a dictionary, see [4.4. Define dictionary](#44-define-dictionary). |
| 125 | +
|
| 126 | +### 4.3. Initialize utility |
| 127 | + |
| 128 | +- To load dictionary into `Model` instance, as well as to parse text, the |
| 129 | +`Utility` instance is required: |
| 130 | + |
| 131 | +```python |
| 132 | +r = pilsner.Utility() |
| 133 | +``` |
| 134 | + |
| 135 | +### 4.4. Define dictionary |
| 136 | + |
| 137 | +- Source dictionary for `pilsner` must be delimited text file. |
| 138 | +- Along with the source dictionary, specifications of the columns (fields) must |
| 139 | +be provided as list where each item corresponds to a column (from left to |
| 140 | +right). Each item in this list must be a dict object with string keys `name`, |
| 141 | +`include`, `delimiter`, `id_flag`, `normalizer_flag`, and `value_flag`, so |
| 142 | +that: |
| 143 | + - `field['name']` is a string for column title; |
| 144 | + - `field['include']` is a boolean that must be set to `True` for the column |
| 145 | + to be included in the model, otherwise `False`; |
| 146 | + - `field['delimiter']` is a string that is supposed to split single cell into |
| 147 | + list of values if the column holds concatenated lists rather than individual |
| 148 | + values; |
| 149 | + - `field['id_flag]` is a boolean that must be set to `True` if the column is |
| 150 | + supposed to be used for grouping synonyms (generally, entity ID is such |
| 151 | + column), otherwise `False`; |
| 152 | + - `field['normalizer_flag']` is a boolean that must be set to `True` if the |
| 153 | + column holds indication on what normalization unit must be applied to this |
| 154 | + particular synonym, otherwise `False`; |
| 155 | + - `field['value_flag']` is a boolean that must be set to `True` if the column |
| 156 | + holds synonyms that are supposed to be looked up when parsing a text, |
| 157 | + otherwise `False`. |
| 158 | + |
| 159 | +> If dictionary has a column flagged with `normalizer_flag`, synonym in each |
| 160 | +> row will be normalized with string normalization unit which name is mapped on |
| 161 | +> value in this column using `pilsner.Model.normalizer_map` dict. If value is |
| 162 | +> not among `pilsner.Model.normalizer_map` keys, default normalization unit |
| 163 | +> will be used. |
| 164 | +
|
| 165 | +### 4.5. Compile model |
| 166 | + |
| 167 | +- To store dictionary in `Model` instance, method `compile_model` of `Utility` |
| 168 | +instance must be called with the following required parameters: |
| 169 | + - `model`: pointer to initilized `Model` instance; |
| 170 | + - `filename`: string with path and filename of source dictionary; |
| 171 | + - `fields`: dict object with definitions of columns (see |
| 172 | + [4.4. Define dictionary](#44-define-dictionary)); |
| 173 | + - `word_separator`: string defining what is to be considered word separator |
| 174 | + (generally, it should be whitespace); |
| 175 | + - `column_separator`: string defining what is to be considered column |
| 176 | + separator (e.g. `\t` for tab-delimited file); |
| 177 | + - `column_enclosure`: string defining what is to be stripped away from cell |
| 178 | + after row has been split into columns (typically, it should be `\n` for new |
| 179 | + line character to be trimmed from the rightmost column). |
| 180 | + |
| 181 | +```python |
| 182 | +# Assuming m is pilsner.Model instance and r is pilsner.Utility instance: |
| 183 | +r.compile_model( |
| 184 | + model=m, |
| 185 | + filename='path/to/dictionary_in_a_text_file.txt', |
| 186 | + fields=fields, |
| 187 | + word_separator=' ', |
| 188 | + column_separator='\t', |
| 189 | + column_enclosure='\n' |
| 190 | +) |
| 191 | +``` |
| 192 | + |
| 193 | +- To review optional parameters, see comments in the code. |
| 194 | + |
| 195 | +### 4.6. Save model |
| 196 | + |
| 197 | +- If `Model` instance has compiled dictionary, and if database location for the |
| 198 | +`Model` instance is not explicitly set to `':memory:'`, the data such instance |
| 199 | +is holding can be saved to disk: |
| 200 | + |
| 201 | +```python |
| 202 | +# Assuming m is pilsner.Model instance |
| 203 | +m.save('path/to/model_name') |
| 204 | +``` |
| 205 | + |
| 206 | +- The snippet above will write the following files: |
| 207 | + - `path/to/model_name.attributes`: database with attributes (fields from the |
| 208 | + dictionary that are not synonyms); |
| 209 | + - `path/to/model_name.keywords`: keywords used for disambiguation; |
| 210 | + - `path/to/model_name.normalizers`: string normalization units; |
| 211 | + - `path/to/model_name.0.dictionary`: trie with synonyms; |
| 212 | + - `path/to/model_name.<N>.dictionary`: additional tries with synonyms (`<N>` |
| 213 | + being integer number of a trie) in case more than one trie was created (see |
| 214 | + comments in the code - `pilsner.Utility.compile_model` method, `item_limit` |
| 215 | + parameter). |
| 216 | + |
| 217 | +### 4.7. Load model |
| 218 | + |
| 219 | +- To initialize new `Model` instance using previously saved data: |
| 220 | + |
| 221 | +```python |
| 222 | +m = pilsner.Model(filename='path/to/model_name') |
| 223 | +``` |
| 224 | + |
| 225 | +- Alternatively, data can be loaded to previously initialized `Model` instance: |
| 226 | + |
| 227 | +```python |
| 228 | +m = pilsner.Model() |
| 229 | +m.load('path/to/model_name') |
| 230 | +``` |
| 231 | + |
| 232 | +- In both cases, the program will look for the following files: |
| 233 | + - `path/to/model_name.attributes`: database with attributes (fields from the dictionary that are not synonyms); |
| 234 | + - `path/to/model_name.keywords`: keywords used for disambiguation; |
| 235 | + - `path/to/model_name.normalizers`: string normalization units; |
| 236 | + - `path/to/model_name.<N>.dictionary`: tries with synonyms (`<N>` being |
| 237 | + integer). |
| 238 | + |
| 239 | +### 4.8. Parse string |
| 240 | + |
| 241 | +- To parse a string without filtering out any synonyms and output all |
| 242 | +attributes of spotted entities: |
| 243 | + |
| 244 | +```python |
| 245 | +# Assuming m is pilsner.Model instance, r is pilsner.Utility instance, |
| 246 | +# and text_to_parse is string to parse |
| 247 | +parsed = r.parse( |
| 248 | + model=m, |
| 249 | + source_string=text_to_parse |
| 250 | +) |
| 251 | +``` |
| 252 | + |
| 253 | +- The output will be dict object where keys are tuples for location of spotted |
| 254 | +entity in a string (begin, end) and values are dicts for attributes that are |
| 255 | +associated with identified entity (`{'attribute_name': {attribute_values}}`). |
| 256 | +- For details about optional parameters, see comments in the code - |
| 257 | +`pilsner.Utility.parse` function). |
| 258 | + |
| 259 | +## 5. Example |
| 260 | + |
| 261 | +Everything written above is put together in example code, |
| 262 | +see **/misc/example/** directory in the project's repository. |
0 commit comments