Word forms
Word forms are applied after tokenizing incoming text by charset_table rules. They essentially let you replace one word with another. Normally, that would be used to bring different word forms to a single normal form (e.g. to normalize all the variants such as “walks”, “walked”, “walking” to the normal form “walk”). It can also be used to implement stemming exceptions, because stemming is not applied to words found in the forms list.
wordforms
wordforms = path/to/wordforms.txt
wordforms = path/to/alternateforms.txt
wordforms = path/to/dict*.txt
Word forms dictionary. Optional, default is empty.
The dictionaries are used to normalize incoming words both during indexing and searching. Therefore, when it comes to a plain table, it’s required to rotate the table in order to pick up changes in the word forms file.
Word forms support in Manticore is designed to handle large dictionaries well. They moderately affect indexing speed; for example, a dictionary with 1 million entries slows down indexing by about 1.5 times. Searching speed is not affected at all. The additional RAM impact is roughly equal to the dictionary file size, and dictionaries are shared across tables. For instance, if the very same 50 MB word forms file is specified for 10 different tables, the additional searchd
RAM usage will be about 50 MB.
The dictionary file should be in a simple plain text format. Each line should contain source and destination word forms, in UTF-8 encoding, separated by a “greater than” sign. Rules from the charset_table will be applied when the file is loaded, so if you are using built-in charset_table
options, it is typically case-insensitive, just like your other full-text indexed data. Here is a sample file contents:
walks > walk
walked > walk
walking > walk
There is a bundled utility called Spelldump that helps you create a dictionary file in a format that Manticore can read. The utility can read from source .dict
and .aff
dictionary files in the ispell
or MySpell
format, as bundled with OpenOffice.
You can map several source words to a single destination word. The process happens on tokens, not the source text, so differences in whitespace and markup are ignored.
You can use the =>
symbol instead of >
. Comments (starting with #
) are also allowed. Finally, if a line starts with a tilde (~
), the wordform will be applied after morphology, instead of before (note that only a single source word is supported in this case).
core 2 duo > c2d
e6600 > c2d
core 2duo => c2d # Some people write '2duo' together...
~run > walk # Along with stem_en morphology enabled replaces 'run', 'running', 'runs' (and any other words that stem to just 'run') to 'walk'
You can specify multiple destination tokens:
s02e02 > season 2 episode 2
s3 e3 > season 3 episode 3
You can specify multiple files, not just one. Masks can be used as a pattern, and all matching files will be processed in simple ascending order.
In the RT mode, only absolute paths are allowed.
If multi-byte codepages are used and file names include foreign characters, the resulting order may not be exactly alphabetic. If the same wordform definition is found in multiple files, the latter one is used and overrides previous definitions.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) wordforms = '/var/lib/manticore/wordforms.txt' wordforms = '/var/lib/manticore/alternateforms.txt /var/lib/manticore/dict*.txt'
POST /cli -d "
CREATE TABLE products(title text, price float) wordforms = '/var/lib/manticore/wordforms.txt' wordforms = '/var/lib/manticore/alternateforms.txt' wordforms = '/var/lib/manticore/dict*.txt'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'wordforms' => [
'/var/lib/manticore/wordforms.txt',
'/var/lib/manticore/alternateforms.txt',
'/var/lib/manticore/dict*.txt'
]
]);
utilsApi.sql('CREATE TABLE products(title text, price float) wordforms = \'/var/lib/manticore/wordforms.txt\' wordforms = \'/var/lib/manticore/alternateforms.txt\' wordforms = \'/var/lib/manticore/dict*.txt\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float)wordforms = \'/var/lib/manticore/wordforms.txt\' wordforms = \'/var/lib/manticore/alternateforms.txt\' wordforms = \'/var/lib/manticore/dict*.txt\'');
utilsApi.sql("CREATE TABLE products(title text, price float) wordforms = '/var/lib/manticore/wordforms.txt' wordforms = '/var/lib/manticore/alternateforms.txt' wordforms = '/var/lib/manticore/dict*.txt'");
table products {
wordforms = /var/lib/manticore/wordforms.txt
wordforms = /var/lib/manticore/alternateforms.txt
wordforms = /var/lib/manticore/dict*.txt
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
Exceptions
Exceptions (also known as synonyms) allow to map one or more tokens (including tokens with characters that would normally be excluded) to a single keyword. They are similar to wordforms in that they also perform mapping, but have a number of important differences.
Short summary of the differences from wordforms is as follows:
Exceptions | Word forms |
---|---|
Case sensitive | Case insensitive |
Can use special characters that are not in charset_table | Fully obey charset_table |
Underperform on huge dictionaries | Designed to handle millions of entries |
exceptions
exceptions = path/to/exceptions.txt
Tokenizing exceptions file. Optional, default is empty. In the RT mode only absolute paths are allowed.
The expected file format is also plain text, with one line per exception, and the line format is as follows:
map-from-tokens => map-to-token
Example file:
at & t => at&t
AT&T => AT&T
Standarten Fuehrer => standartenfuhrer
Standarten Fuhrer => standartenfuhrer
MS Windows => ms windows
Microsoft Windows => ms windows
C++ => cplusplus
c++ => cplusplus
C plus plus => cplusplus
All tokens here are case sensitive: they will not be processed by charset_table rules. Thus, with the example exceptions file above, “at&t” text will be tokenized as two keywords “at” and “t”, because of lowercase letters. On the other hand, “AT&T” will match exactly and produce single “AT&T” keyword.
Note that this map-to keyword is:
- always interpreted as a single word
- and is both case and space sensitive
In our sample, “ms windows” query will not match the document with “MS Windows” text. The query will be interpreted as a query for two keywords, “ms” and “windows”. And what “MS Windows” gets mapped to is a single keyword “ms windows”, with a space in the middle. On the other hand, “standartenfuhrer” will retrieve documents with “Standarten Fuhrer” or “Standarten Fuehrer” contents (capitalized exactly like this), or any capitalization variant of the keyword itself, eg. “staNdarTenfUhreR”. (It won’t catch “standarten fuhrer”, however: this text does not match any of the listed exceptions because of case sensitivity, and gets indexed as two separate keywords.)
Whitespace in the map-from tokens list matters, but its amount does not. Any amount of the whitespace in the map-form list will match any other amount of whitespace in the indexed document or query. For instance, “AT & T” map-from token will match “AT & T” text, whatever the amount of space in both map-from part and the indexed text. Such text will therefore be indexed as a special “AT&T” keyword, thanks to the very first entry from the sample.
Exceptions also allow to capture special characters (that are exceptions from general charset_table rules; hence the name). Assume that you generally do not want to treat ‘+’ as a valid character, but still want to be able search for some exceptions from this rule such as ‘C++’. The sample above will do just that, totally independent of what characters are in the table and what are not.
Exceptions are applied to raw incoming document and query data during indexing and searching respectively. Therefore, when it comes to a plain table to pick up changes in the file it’s required to reindex and restart searchd
.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) exceptions = '/usr/local/manticore/data/exceptions.txt'
POST /cli -d "
CREATE TABLE products(title text, price float) exceptions = '/usr/local/manticore/data/exceptions.txt'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'exceptions' => '/usr/local/manticore/data/exceptions.txt'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) exceptions = \'/usr/local/manticore/data/exceptions.txt\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) exceptions = \'/usr/local/manticore/data/exceptions.txt\'');
utilsApi.sql("CREATE TABLE products(title text, price float) exceptions = '/usr/local/manticore/data/exceptions.txt'");
table products {
exceptions = /usr/local/manticore/data/exceptions.txt
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
Advanced morphology
Morphology preprocessors can be applied to the words being indexed to replace different forms of the same word with the base, normalized form or improve segmentation. For instance, English stemmer will normalize both “dogs” and “dog” to “dog”, making search results for the both keywords the same.
There are 4 different morphology preprocessors that Manticore implements.
- Lemmatizer reduces a keyword form to a so-called lemma, a proper normal form, or in other words, a valid natural language root word. For example, “running” could be reduced to “run”, the infinitive verb form, and “octopi” would be reduced to “octopus”, the singular noun form. Note that sometimes a word form can have multiple corresponding root words. For instance, by looking at “dove” it is not possible to tell whether this is a past tense of “dive” the verb as in “He dove into a pool.”, or “dove” the noun as in “White dove flew over the cuckoo’s nest.” In this case lemmatizer can generate all the possible root forms.
- Stemmer reduces a keyword form to a so-called stem by removing and/or replacing certain well-known suffixes. The resulting stem is however not guaranteed to be a valid word on itself. For instance, with a Porter English stemmers “running” would still reduce to “run”, which is fine, but “business” would reduce to “busi”, which is not a word, and “octopi” would not reduce at all. Stemmers are essentially (much) simpler but still pretty good replacements of full-blown lemmatizers.
- Phonetic algorithms replace the words with specially crafted phonetic codes that are equal even when the words original are different, but phonetically close.
- Word breaking algorithms split text into words. Currently available only for Chinese.
morphology
morphology = morphology1[, morphology2, ...]
A list of morphology preprocessors to apply. Optional, default is empty (do not apply any preprocessor).
The morphology processors that come with our own built-in Manticore implementations are:
- English, Russian, and German lemmatizers
- English, Russian, Arabic, and Czech stemmers
- SoundEx and MetaPhone phonetic algorithms
- Chinese word breaking algorithm
- Snowball (libstemmer) stemmers for more than 15 other languages.
Lemmatizers require dictionary .pak
files that you can download from the website. The dictionaries needs to be put in the directory specified by lemmatizer_base. Also, there is the lemmatizer_cache setting which lets you speed up lemmatizing (and therefore indexing) by spending more RAM for, basically, an uncompressed dictionary cache.
The Chinese language segmentation using ICU is also available. It is a much more precise, but a little bit slower way (compared to n-grams) to segment Chinese documents. charset_table must contain all Chinese characters (you can use alias “cjk”). In case of “morphology=icu_chinese” documents are first pre-processed by ICU, then the result is processed by the tokenizer (according to your charset_table) and then other morphology processors specified in the “morphology” option are applied. When the documents are processed by ICU, only those parts of texts that contain Chinese are passed to ICU for segmentation, others can be modified by other means (different morphologies, charset_table etc.)
Built-in English and Russian stemmers should be faster than their libstemmer counterparts, but can produce slightly different results, because they are based on an older version.
Soundex implementation matches that of MySQL. Metaphone implementation is based on Double Metaphone algorithm and indexes the primary code.
Built-in values that are available for use in the morphology
option are as follows:
- none - do not perform any morphology processing
- lemmatize_ru - apply Russian lemmatizer and pick a single root form
- lemmatize_uk - apply Ukrainian lemmatizer and pick a single root form (install it first in Centos or Ubuntu/Debian). For correct work of the lemmatizer make sure specific Ukrainian characters are preserved in your
charset_table
since by default they are not. For that override them, like this:charset_table='non_cjk,U+0406->U+0456,U+0456,U+0407->U+0457,U+0457,U+0490->U+0491,U+0491'
. Here is an interactive course about how to install and use the urkainian lemmatizer. - lemmatize_en - apply English lemmatizer and pick a single root form
- lemmatize_de - apply German lemmatizer and pick a single root form
- lemmatize_ru_all - apply Russian lemmatizer and index all possible root forms
- lemmatize_uk_all - apply Ukrainian lemmatizer and index all possible root forms. Find the installation links above and take care of the
charset_table
. - lemmatize_en_all - apply English lemmatizer and index all possible root forms
- lemmatize_de_all - apply German lemmatizer and index all possible root forms
- stem_en - apply Porter’s English stemmer
- stem_ru - apply Porter’s Russian stemmer
- stem_enru - apply Porter’s English and Russian stemmers
- stem_cz - apply Czech stemmer
- stem_ar - apply Arabic stemmer
- soundex - replace keywords with their SOUNDEX code
- metaphone - replace keywords with their METAPHONE code
- icu_chinese - apply Chinese text segmentation using ICU
- libstemmer_* . Refer to the list of supported languages for details
Several stemmers can be specified at once comma-separated. They will be applied to incoming words in the order they are listed, and the processing will stop once one of the stemmers actually modifies the word. Also when wordforms feature is enabled the word will be looked up in word forms dictionary first, and if there is a matching entry in the dictionary, stemmers will not be applied at all. Or in other words, wordforms can be used to implement stemming exceptions.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) morphology = 'stem_en, libstemmer_sv'
POST /cli -d "CREATE TABLE products(title text, price float) morphology = 'stem_en, libstemmer_sv'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'morphology' => 'stem_en, libstemmer_sv'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) morphology = \'stem_en, libstemmer_sv\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) morphology = \'stem_en, libstemmer_sv\'');
utilsApi.sql("CREATE TABLE products(title text, price float) morphology = 'stem_en, libstemmer_sv'");
table products {
morphology = stem_en, libstemmer_sv
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
morphology_skip_fields
morphology_skip_fields = field1[, field2, ...]
A list of fields to skip morphology preprocessing. Optional, default is empty (apply preprocessors to all fields).
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, name text, price float) morphology_skip_fields = 'name' morphology = 'stem_en'
POST /cli -d "
CREATE TABLE products(title text, name text, price float) morphology_skip_fields = 'name' morphology = 'stem_en'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'morphology_skip_fields' => 'name',
'morphology' => 'stem_en'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) morphology_skip_fields = \'name\' morphology = \'stem_en\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) morphology_skip_fields = \'name\' morphology = \'stem_en\'');
utilsApi.sql("CREATE TABLE products(title text, price float) morphology_skip_fields = 'name' morphology = 'stem_en'");
table products {
morphology_skip_fields = name
morphology = stem_en
type = rt
path = tbl
rt_field = title
rt_field = name
rt_attr_uint = price
}
min_stemming_len
min_stemming_len = length
Minimum word length at which to enable stemming. Optional, default is 1 (stem everything).
Stemmers are not perfect, and might sometimes produce undesired results. For instance, running “gps” keyword through Porter stemmer for English results in “gp”, which is not really the intent. min_stemming_len
feature lets you suppress stemming based on the source word length, ie. to avoid stemming too short words. Keywords that are shorter than the given threshold will not be stemmed. Note that keywords that are exactly as long as specified will be stemmed. So in order to avoid stemming 3-character keywords, you should specify 4 for the value. For more finely grained control, refer to wordforms feature.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) min_stemming_len = '4' morphology = 'stem_en'
POST /cli -d "
CREATE TABLE products(title text, price float) min_stemming_len = '4' morphology = 'stem_en'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'min_stemming_len' => '4',
'morphology' => 'stem_en'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) min_stemming_len = \'4\' morphology = \'stem_en\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) min_stemming_len = \'4\' morphology = \'stem_en\'');
utilsApi.sql("CREATE TABLE products(title text, price float) min_stemming_len = '4' morphology = 'stem_en'");
table products {
min_stemming_len = 4
morphology = stem_en
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
index_exact_words
index_exact_words = {0|1}
Whether to index the original keywords along with the stemmed/remapped versions. Optional, default is 0 (do not index).
When enabled, index_exact_words
forces indexation to put the raw keywords in the full-text index along with the stemmed versions. That, in turn, enables exact form operator in the query language to work. This impacts the full-text index size and the indexing time. However, searching performance is not impacted at all.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) index_exact_words = '1' morphology = 'stem_en'
POST /cli -d "
CREATE TABLE products(title text, price float) index_exact_words = '1' morphology = 'stem_en'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'index_exact_words' => '1',
'morphology' => 'stem_en'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) index_exact_words = \'1\' morphology = \'stem_en\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) index_exact_words = \'1\' morphology = \'stem_en\'');
utilsApi.sql("CREATE TABLE products(title text, price float) index_exact_words = '1' morphology = 'stem_en'");
table products {
index_exact_words = 1
morphology = stem_en
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}