Coder Social home page Coder Social logo

natasha / yargy Goto Github PK

View Code? Open in Web Editor NEW
311.0 311.0 41.0 671 KB

Rule-based facts extraction for Russian language

License: MIT License

Python 99.81% Makefile 0.19%
earley-parser information-extraction morphology nlp python russian tomita tomita-parser

yargy's Introduction

CI

Natasha solves basic NLP tasks for Russian language: tokenization, sentence segmentation, word embedding, morphology tagging, lemmatization, phrase normalization, syntax parsing, NER tagging, fact extraction. Quality on every task is similar or better than current SOTAs for Russian language on news articles, see evaluation section. Natasha is not a research project, underlying technologies are built for production. We pay attention to model size, RAM usage and performance. Models run on CPU, use Numpy for inference.

Natasha integrates libraries from Natasha project under one convenient API:

  • Razdel — token, sentence segmentation for Russian
  • Navec — compact Russian embeddings
  • Slovnet — modern deep-learning techniques for Russian NLP, compact models for Russian morphology, syntax, NER.
  • Yargy — rule-based fact extraction similar to Tomita parser.
  • Ipymarkup — NLP visualizations for NER and syntax markups.

⚠ API may change, for realworld tasks consider using low level libraries from Natasha project. Models optimized for news articles, quality on other domain may be lower. To use old NamesExtractor, AddressExtactor downgrade pip install natasha<1 yargy<0.13

Install

Natasha supports Python 3.7+ and PyPy3:

$ pip install natasha

Usage

Import, initialize modules, build Doc object.

>>> from natasha import (
    Segmenter,
    MorphVocab,
    
    NewsEmbedding,
    NewsMorphTagger,
    NewsSyntaxParser,
    NewsNERTagger,
    
    PER,
    NamesExtractor,

    Doc
)


>>> segmenter = Segmenter()
>>> morph_vocab = MorphVocab()

>>> emb = NewsEmbedding()
>>> morph_tagger = NewsMorphTagger(emb)
>>> syntax_parser = NewsSyntaxParser(emb)
>>> ner_tagger = NewsNERTagger(emb)

>>> names_extractor = NamesExtractor(morph_vocab)

>>> text = 'Посол Израиля на Украине Йоэль Лион признался, что пришел в шок, узнав о решении властей Львовской области объявить 2019 год годом лидера запрещенной в России Организации украинских националистов (ОУН) Степана Бандеры. Свое заявление он разместил в Twitter. «Я не могу понять, как прославление тех, кто непосредственно принимал участие в ужасных антисемитских преступлениях, помогает бороться с антисемитизмом и ксенофобией. Украина не должна забывать о преступлениях, совершенных против украинских евреев, и никоим образом не отмечать их через почитание их исполнителей», — написал дипломат. 11 декабря Львовский областной совет принял решение провозгласить 2019 год в регионе годом Степана Бандеры в связи с празднованием 110-летия со дня рождения лидера ОУН (Бандера родился 1 января 1909 года). В июле аналогичное решение принял Житомирский областной совет. В начале месяца с предложением к президенту страны Петру Порошенко вернуть Бандере звание Героя Украины обратились депутаты Верховной Рады. Парламентарии уверены, что признание Бандеры национальным героем поможет в борьбе с подрывной деятельностью против Украины в информационном поле, а также остановит «распространение мифов, созданных российской пропагандой». Степан Бандера (1909-1959) был одним из лидеров Организации украинских националистов, выступающей за создание независимого государства на территориях с украиноязычным населением. В 2010 году в период президентства Виктора Ющенко Бандера был посмертно признан Героем Украины, однако впоследствии это решение было отменено судом. '
>>> doc = Doc(text)

Segmentation

Split text into tokens and sentencies. Defines tokens and sents properties of doc. Uses Razdel internally.

>>> doc.segment(segmenter)
>>> print(doc.tokens[:5])
>>> print(doc.sents[:5])
[DocToken(stop=5, text='Посол'),
 DocToken(start=6, stop=13, text='Израиля'),
 DocToken(start=14, stop=16, text='на'),
 DocToken(start=17, stop=24, text='Украине'),
 DocToken(start=25, stop=30, text='Йоэль')]
[DocSent(stop=218, text='Посол Израиля на Украине Йоэль Лион признался, чт..., tokens=[...]),
 DocSent(start=219, stop=257, text='Свое заявление он разместил в Twitter.', tokens=[...]),
 DocSent(start=258, stop=424, text=Я не могу понять, как прославление тех, кто непо..., tokens=[...]),
 DocSent(start=425, stop=592, text='Украина не должна забывать о преступлениях, совер..., tokens=[...]),
 DocSent(start=593, stop=798, text='11 декабря Львовский областной совет принял решен..., tokens=[...])]

Morphology

For every token extract rich morphology tags. Depends on segmentation step. Defines pos and feats properties of doc.tokens. Uses Slovnet morphology model internally.

Call morph.print() to visualize morphology markup.

>>> doc.tag_morph(morph_tagger)
>>> print(doc.tokens[:5])
>>> doc.sents[0].morph.print()
[DocToken(stop=5, text='Посол', pos='NOUN', feats=<Anim,Nom,Masc,Sing>),
 DocToken(start=6, stop=13, text='Израиля', pos='PROPN', feats=<Inan,Gen,Masc,Sing>),
 DocToken(start=14, stop=16, text='на', pos='ADP'),
 DocToken(start=17, stop=24, text='Украине', pos='PROPN', feats=<Inan,Loc,Fem,Sing>),
 DocToken(start=25, stop=30, text='Йоэль', pos='PROPN', feats=<Anim,Nom,Masc,Sing>)]
               Посол NOUN|Animacy=Anim|Case=Nom|Gender=Masc|Number=Sing
             Израиля PROPN|Animacy=Inan|Case=Gen|Gender=Masc|Number=Sing
                  на ADP
             Украине PROPN|Animacy=Inan|Case=Loc|Gender=Fem|Number=Sing
               Йоэль PROPN|Animacy=Anim|Case=Nom|Gender=Masc|Number=Sing
                Лион PROPN|Animacy=Anim|Case=Nom|Gender=Masc|Number=Sing
           признался VERB|Aspect=Perf|Gender=Masc|Mood=Ind|Number=Sing|Tense=Past|VerbForm=Fin|Voice=Mid
                   , PUNCT
                 что SCONJ
...

Lemmatization

Lemmatize every token. Depends on morphology step. Defines lemma property of doc.tokens. Uses Pymorphy internally.

>>> for token in doc.tokens:
>>>     token.lemmatize(morph_vocab)
    
>>> print(doc.tokens[:5])
>>> {_.text: _.lemma for _ in doc.tokens}
[DocToken(stop=5, text='Посол', pos='NOUN', feats=<Anim,Nom,Masc,Sing>, lemma='посол'),
 DocToken(start=6, stop=13, text='Израиля', pos='PROPN', feats=<Inan,Gen,Masc,Sing>, lemma='израиль'),
 DocToken(start=14, stop=16, text='на', pos='ADP', lemma='на'),
 DocToken(start=17, stop=24, text='Украине', pos='PROPN', feats=<Inan,Loc,Fem,Sing>, lemma='украина'),
 DocToken(start=25, stop=30, text='Йоэль', pos='PROPN', feats=<Anim,Nom,Masc,Sing>, lemma='йоэль')]
{'Посол': 'посол',
 'Израиля': 'израиль',
 'на': 'на',
 'Украине': 'украина',
 'Йоэль': 'йоэль',
 'Лион': 'лион',
 'признался': 'признаться',
 ',': ',',
 'что': 'что',
 'пришел': 'прийти',
 'в': 'в',
 'шок': 'шок',
 'узнав': 'узнать',
 'о': 'о',
...

Syntax

For every sentence run syntax analyzer. Depends on segmentation step. Defines id, head_id, rel properties of doc.tokens. Uses Slovnet syntax model internally.

Use syntax.print() to visualize syntax markup. Uses Ipymarkup internally.

>>> doc.parse_syntax(syntax_parser)
>>> print(doc.tokens[:5])
>>> doc.sents[0].syntax.print()
[DocToken(stop=5, text='Посол', id='1_1', head_id='1_7', rel='nsubj', pos='NOUN', feats=<Anim,Nom,Masc,Sing>),
 DocToken(start=6, stop=13, text='Израиля', id='1_2', head_id='1_1', rel='nmod', pos='PROPN', feats=<Inan,Gen,Masc,Sing>),
 DocToken(start=14, stop=16, text='на', id='1_3', head_id='1_4', rel='case', pos='ADP'),
 DocToken(start=17, stop=24, text='Украине', id='1_4', head_id='1_1', rel='nmod', pos='PROPN', feats=<Inan,Loc,Fem,Sing>),
 DocToken(start=25, stop=30, text='Йоэль', id='1_5', head_id='1_1', rel='appos', pos='PROPN', feats=<Anim,Nom,Masc,Sing>)]
        ┌──► Посол         nsubjИзраиля       
        │ ┌► на            case
        │ └─ Украине       
        │ ┌─ Йоэль         
        │ └► Лион          flat:name
┌─────┌─└─── признался     
│     │ ┌──► ,             punct
│     │ │ ┌► что           mark
│     └►└─└─ пришел        ccomp
│     │   ┌► в             case
│     └──►└─ шок           obl
│         ┌► ,             punct
│ ┌────►┌─└─ узнав         advcl
│ │     │ ┌► о             case
│ │ ┌───└►└─ решении       obl
│ │ │ ┌─└──► властей       nmod
│ │ │ │   ┌► Львовской     amod
│ │ │ └──►└─ области       nmod
│ └─└►┌─┌─── объявить      nmod
│     │ │ ┌► 2019          amod
│     │ └►└─ год           obj
│     └──►┌─ годом         obl
│   ┌─────└► лидера        nmod
│   │ ┌►┌─── запрещенной   acl
│   │ │ │ ┌► в             case
│   │ │ └►└─ России        obl
│ ┌─└►└─┌─── Организации   nmod
│ │     │ ┌► украинских    amod
│ │   ┌─└►└─ националистов nmod
│ │   │   ┌► (             punct
│ │   └►┌─└─ ОУН           parataxis
│ │     └──► )             punct
│ └──────►┌─ Степана       appos
│         └► Бандеры       flat:name
└──────────► .             punct
...

NER

Extract standart named entities: names, locations, organizations. Depends on segmentation step. Defines spans property of doc. Uses Slovnet NER model internally.

Call ner.print() to visualize NER markup. Uses Ipymarkup internally.

>>> doc.tag_ner(ner_tagger)
>>> print(doc.spans[:5])
>>> doc.ner.print()
[DocSpan(start=6, stop=13, type='LOC', text='Израиля', tokens=[...]),
 DocSpan(start=17, stop=24, type='LOC', text='Украине', tokens=[...]),
 DocSpan(start=25, stop=35, type='PER', text='Йоэль Лион', tokens=[...]),
 DocSpan(start=89, stop=106, type='LOC', text='Львовской области', tokens=[...]),
 DocSpan(start=152, stop=158, type='LOC', text='России', tokens=[...])]
Посол Израиля на Украине Йоэль Лион признался, что пришел в шок, узнав
      LOC────    LOC──── PER───────                                   
 о решении властей Львовской области объявить 2019 год годом лидера 
                   LOC──────────────                                
запрещенной в России Организации украинских националистов (ОУН) 
              LOC─── ORG─────────────────────────────────────── 
Степана Бандеры. Свое заявление он разместил в Twitter. «Я не могу 
PER────────────                                ORG────             
понять, как прославление тех, кто непосредственно принимал участие в 
ужасных антисемитских преступлениях, помогает бороться с 
антисемитизмом и ксенофобией. Украина не должна забывать о 
                              LOC────                      
преступлениях, совершенных против украинских евреев, и никоим образом 
не отмечать их через почитание их исполнителей», — написал дипломат. 
11 декабря Львовский областной совет принял решение провозгласить 2019
           ORG──────────────────────                                  
 год в регионе годом Степана Бандеры в связи с празднованием 110-летия
                     PER────────────                                  
 со дня рождения лидера ОУН (Бандера родился 1 января 1909 года). В 
                        ORG                                         
июле аналогичное решение принял Житомирский областной совет. В начале 
                                ORG────────────────────────           
месяца с предложением к президенту страны Петру Порошенко вернуть 
                                          PER────────────         
Бандере звание Героя Украины обратились депутаты Верховной Рады. 
PER────              LOC────                     ORG───────────  
Парламентарии уверены, что признание Бандеры национальным героем 
                                     PER────                     
поможет в борьбе с подрывной деятельностью против Украины в 
                                                  LOC────   
информационном поле, а также остановит «распространение мифов, 
созданных российской пропагандой». Степан Бандера (1909-1959) был 
                                   PER───────────                 
одним из лидеров Организации украинских националистов, выступающей за 
                 ORG─────────────────────────────────                 
создание независимого государства на территориях с украиноязычным 
населением. В 2010 году в период президентства Виктора Ющенко Бандера 
                                               PER─────────── PER──── 
был посмертно признан Героем Украины, однако впоследствии это решение 
                             LOC────                                  
было отменено судом. 

Named entity normalization

For every NER span apply normalization procedure. Depends on NER, morphology and syntax steps. Defines normal property of doc.spans.

One can not just lemmatize every token inside entity span, otherwise "Организации украинских националистов" would become "Организация украинские националисты". Natasha uses syntax dependencies to produce correct "Организация украинских националистов".

>>> for span in doc.spans:
>>>    span.normalize(morph_vocab)
>>> print(doc.spans[:5])
>>> {_.text: _.normal for _ in doc.spans if _.text != _.normal}
[DocSpan(start=6, stop=13, type='LOC', text='Израиля', tokens=[...], normal='Израиль'),
 DocSpan(start=17, stop=24, type='LOC', text='Украине', tokens=[...], normal='Украина'),
 DocSpan(start=25, stop=35, type='PER', text='Йоэль Лион', tokens=[...], normal='Йоэль Лион'),
 DocSpan(start=89, stop=106, type='LOC', text='Львовской области', tokens=[...], normal='Львовская область'),
 DocSpan(start=152, stop=158, type='LOC', text='России', tokens=[...], normal='Россия')]
{'Израиля': 'Израиль',
 'Украине': 'Украина',
 'Львовской области': 'Львовская область',
 'России': 'Россия',
 'Организации украинских националистов (ОУН)': 'Организация украинских националистов (ОУН)',
 'Степана Бандеры': 'Степан Бандера',
 'Петру Порошенко': 'Петр Порошенко',
 'Бандере': 'Бандера',
 'Украины': 'Украина',
 'Верховной Рады': 'Верховная Рада',
 'Бандеры': 'Бандера',
 'Организации украинских националистов': 'Организация украинских националистов',
 'Виктора Ющенко': 'Виктор Ющенко'}

Named entity parsing

Parse PER named entities into firstname, surname and patronymic. Depends on NER step. Defines fact property of doc.spans. Uses Yargy-parser internally.

Natasha also has built in extractors for dates, money, address.

>>> for span in doc.spans:
>>>    if span.type == PER:
>>>        span.extract_fact(names_extractor)

>>> print(doc.spans[:5])
>>> {_.normal: _.fact.as_dict for _ in doc.spans if _.type == PER}
[DocSpan(start=6, stop=13, type='LOC', text='Израиля', tokens=[...], normal='Израиль'),
 DocSpan(start=17, stop=24, type='LOC', text='Украине', tokens=[...], normal='Украина'),
 DocSpan(start=25, stop=35, type='PER', text='Йоэль Лион', tokens=[...], normal='Йоэль Лион', fact=DocFact(slots=[...])),
 DocSpan(start=89, stop=106, type='LOC', text='Львовской области', tokens=[...], normal='Львовская область'),
 DocSpan(start=152, stop=158, type='LOC', text='России', tokens=[...], normal='Россия')]
{'Йоэль Лион': {'first': 'Йоэль', 'last': 'Лион'},
 'Степан Бандера': {'first': 'Степан', 'last': 'Бандера'},
 'Петр Порошенко': {'first': 'Петр', 'last': 'Порошенко'},
 'Бандера': {'last': 'Бандера'},
 'Виктор Ющенко': {'first': 'Виктор', 'last': 'Ющенко'}}

Documentation

Evaluation

Support

Development

Dev env

python -m venv ~/.venvs/natasha-natasha
source ~/.venvs/natasha-natasha/bin/activate

pip install -r requirements/dev.txt
pip install -e .

python -m ipykernel install --user --name natasha-natasha

Test

make test

Docs

make exec-docs

Release

# Update setup.py version

git commit -am 'Up version'
git tag v1.6.0

git push
git push --tags

# Github Action builds dist and publishes to PyPi

yargy's People

Contributors

arturgspb avatar dveselov avatar harri-pltr avatar kuk avatar takiholadi avatar xepozz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yargy's Issues

length_eq для int

Бывает нужно сделать предикат для чисел определённой длины: почтовый индекст, ИНН, ОКПО и подобное.

Сейчас можно использовать конструкцию типа

INDEX = and_(
    gram('INT'),
    gte(100000),
    lte(999999)
)

Но так не получится обработать случаи типа "010000"

Migrate to regex package

For now tokenizer uses built-in re package for splitting sentences into separate words, but it can't handle some special cases, but regex package can do it right:

>>> text = 'тест ... 1 одiн, Леве́к'
>>> [x.group(0) for x in re.finditer('(\w+)', text)]
['тест', '1', 'одiн', 'Леве', 'к']
>>> [x.group(0) for x in regex.finditer('(\w+)', text)]
['тест', '1', 'одiн', 'Леве́к']

Additional labels for quote matching

Currently, yargy doesn't understands that text can be wrapped in different types of quotes, like 'classic': " and ' and a lot of different typographic quotes: « » / „ “ / “ ” / ‘ ’ / „ ” / and so on

There should be special label(s?), that checks some quote-related stuff:

  1. Type: opening, closing or generic quote
  2. Same type as opening quote (for matching closing quotes)
  3. Same group as opening quote (because without that, « (opening) and “ (closing) quotes, will be matched together)

Нормализация в normalized и dictionary

Текст ещё незаконченный

Напрягает, то что в normalized и dictionary нужно вручную нормализовать слова. Приходится писать

normalized('федеральный'), normalized('налоговый'), normalized('служба')
normalized('российский'), normalized('федерация')

dictionary({
    'удмуртский',
    'чеченский',
    'чувашский',
}),
normalized('республика')
  1. Это криво читается, "чеченский республика"
  2. Нужно в голове проводить нормализацию, можно ошибиться. Забыть где-нибудь "ё" например

Multiinterpretation of all facts attributes

Возможно ли распарсить одно слово проинтерпретировав его фактом с несколькими активными полями?
Пример: хочется находить различные породы собак, а на выходе получать документ,откуда можно будет собирать какую-то статистику.

Breed = fact('Breed',
    ['weight','hight','origin','life span'])

Пока, я так понимаю, есть вариант писать парсер для каждой такой породы, например

BreedCorgi = fact('Breed',
    [attribute('weight', 15.5), attribute('hight', 29.5), attribute(' origin', 'Wales'), attribute('life span', 12)])
CORGI = rule(caseless('корги')).interpretation(BreedCorgi)

Но что делать когда словарь таких пород растет, есть ли какой-то более гуманный способ?
Функция интерпретации, насколько можно судить из справочника, не позволяет одно слово интерпретировать по-разному -- получается, что , если встречается слово "корги", то путем интерпретации и присвоению констаты можно обновить только один атрибут.(т.к. любая новая интерпретацию будет перезаписывать старую)

Добавить возможность разбивать совпадение на несколько фактов

Например, есть такой текст:

Смольный определит победителей конкурса на оформление Дворцового, Троицкого, Большеохтинского, Первого Инженерного и Первого Садового, Пантелеймоновского и Нижнего Лебяжьего мостов в Санкт-Петербурге к Новому 2018 году и Рождеству

Хотелось бы иметь возможность извлекать название каждого моста по отдельности:

Location(name='дворцовый мост'),
Location(name='троицкий мост'),
Location(name='большеохтинский мост'),
...

Из документации не понял как пропускать некоторые части предложения при разборе

Например у меня есть:

"Привет! Вышлите плиз отчет по клиенту Трололо за июль 2017"

Структура у меня такая

EntityFact = fact(
    'EntityFact',
    ['do', 'action', 'entity', 'name', 'date']
)

Т.е.
do = вышлите
action = отчет
entity = по клиенту
name = Трололо
date = июль 2017

Часть кода, который разбирает это

class DoPipeline(MorphPipeline):
    grammemes = {'Do'}
    keys = [
        'скиньте',
        'вышлите',
        'дайте',
        'сделайте',
        'пришлите'
    ]


class ActionPipeline(MorphPipeline):
    grammemes = {'Action'}
    keys = [
        'статистика',
        'отчет',
        'закрывающие документы',
        'закрывашки',
        'акты'
    ]

Как мне сделать так, чтобы парсер не учитывал неизвестные ему части предложения между "do" и "action".
Т.е. чтобы он фактически не воспринимал ничего лишнего между "Вышлите" и "отчет", сейчас всякие "плиз", "быстрее" и пр делают так, что парсер отказывается парсить.
Тоже самое с датами - люди пишут "... Трололо за ДАТА" или просто "... Трололо ДАТА" при этом не хочется вести список разрешенных предлогов.

Есть варианты?

Do not decode roman numbers

Есть урл http://vilayatiu.co.cc . Ожидается что он разобьётся на токены http : / / vilayatiu . co . cc. Но он разбивается на http : / / vilayatiu . co . 0.

сс принимается за римскую цифру. cc декодируется в 0, потому что decode_roman_number предполагает, что число будет записано большими буквами.

Предлагаю не декодировать римские цифры. И не убирать LATN с них

Короткое название вместо interpretation

Привет. Часто приходится интерпретировать деревья, и постоянно писать слово interpretation. Оно сложное (erpre), сильно раздувает грамматику, с точки зрения чтения кода становится трудно ориентироваться.

Может, попробуем добавить альяс для него? Например, means:

VESTIBULE = rule(or_(
    rule(
        ...
    ).means(Fact.one),
    rule(
        ...
    ).means(Fact.two),
).means(Fact)

Кажется, это ничего не ломает, но сильно упрощает жизнь.

A (,? B)? (,? C)? не метчит A C

A = rule('a')
B = rule('b')
C = rule('c')
SEP = eq(',')
MAYBE_SEP = SEP.optional()

RULE = rule(
    A,
    rule(
        MAYBE_SEP,
        B
    ).optional(),
    rule(
        MAYBE_SEP,
        C
    ).optional()
)
parser = Parser(RULE)
line = 'a c'
list(parser.match(line))
[]


RULE.normalized.as_bnf
R0 -> 'a' R1 R2
R1 -> R3 'b' | R4
R2 -> R3 'c' | R5
R3 -> ',' | R6
R4 -> e
R5 -> e
R6 -> e


parser.chart(line)
0 None
----------------
[0:0] R0 -> $ 'a' R1 R2

1 Token('a', (0, 1), [Form('a', {'LATN'})])
----------------
[0:1] R0 -> 'a' $ R1 R2
[1:1] R0 -> $ 'a' R1 R2
[1:1] R1 -> $ R3 'b'
[1:1] R1 -> $ R4
[1:1] R3 -> $ ','
[1:1] R3 -> $ R6
[1:1] R4 -> $
[1:1] R6 -> $
[1:1] R1 -> R4 $
[1:1] R3 -> R6 $
[0:1] R0 -> 'a' R1 $ R2
[1:1] R1 -> R3 $ 'b'
[1:1] R2 -> $ R3 'c'
[1:1] R2 -> $ R5
[1:1] R5 -> $
[1:1] R2 -> R5 $
[0:1] R0 -> 'a' R1 R2 $

2 Token('c', (2, 3), [Form('c', {'LATN'})])
----------------
[2:2] R0 -> $ 'a' R1 R2

Получение (части) исходной строки из Match

Не нашёл, как иначе выдрать строку из Match. Пользуюсь пока вот так:

def to_slice(span):
    return slice(span.start, span.stop)

matches = extractor(text)
match = matches[0]
result = text[to_slice(match.span)]

Разрешаю скопировать код to_slice в библиотеку :)

Missing punctuation after tokenizer

Есть строчка "см. № 154", ожидается, что она разобьётся на токены "см" "." "№" 154. Но она разбивается на "см" "." 154. Аналогичная проблема с "Ханты – Мансийского", где ord('–') = 8211

Наверное, сложно идеально помечать всю пунктуацию, как PUNCT. Но предлагаю её хотя бы не выкидывать.

Add support for custom normalization grammemes

This ability required for some entities, like Соединенные Штаты Америки, that must be normalized as plural (and now its normalized form is Соединить Штат Америки)

This, also, can be done via passing custom normalization method:

from yargy.normalization import get_inflected_text

def my_normalization_method(tokens):
    return get_inflected_text(tokens, {'plur', 'nomn'})

{
    'labels': [ ... ],
    'normalization': my_normalization_method,
}

Remove INT-RANGE, INT-SEPARATED, FLOAT, FLOAT-RANGE, EMAIL, PHONE tokenizer rules

Например, нужно распарсить дату 3.12.2008 . Ожидается, что она будет разбита на 5 частей: 3, '.', 12, '.', 2008. Из-за FLOAT она будет разбита на 3 части: 3.12, '.', 2008. Это неожиданно, с такими токенами неудобно работать.

Для INT-RANGE, INT-SEPARATED, FLOAT-RANGE, EMAIL, PHONE у меня нет подобных примеров, но я уверен они существуют.

Предлагаю убрать эти правила из токенизатора.

Repeatable and optional at the end of grammar

GRAMMAR = Grammar('G', [
    OR(
        [
            {
                'labels': [
                    eq('а')
                ],
                'repeatable': True
            },
        ],
    ),
])


parser = Parser([GRAMMAR])

strings = [
    'а'
]

for string in strings:
    print(string)
	# NO MATCH
    for grammar, tokens in parser.extract(string):
        guess = [_.value for _ in tokens]
        print('\t', guess)
GRAMMAR = Grammar('G', [
    {
        'labels': [
            eq('б')
        ],
    },
    {
        'labels': [
            eq('а')
        ],
        'repeatable': True
    },
])



parser = Parser([GRAMMAR])

strings = [
    'б а б а'
]

for string in strings:
    print(string)
    for grammar, tokens in parser.extract(string):
        guess = [_.value for _ in tokens]
        print('\t', guess)


EXPECTED 
б а б а
         ['б', 'а']
         ['б', 'а']

GOT
б а б а
         ['б', 'а']

gnc_match with repeatable, optional

gnc_match не всегда работает с repeatable, optional. Например, есть правило.

from yargy import Grammar, Parser
from yargy.labels import gram, gnc_match

G = Grammar(None, [
    {
        'labels': [
            gram('ADJF')
        ],
        'repeatable': True
    },
    {
        'labels': [
            gram('NOUN'),
            gnc_match(-1)
        ],
    }
])
parser = Parser([G])

Корректно метчится:

(_, tokens), = parser.extract('белые чёрные гуси')
[_.value for _ in tokens]
['белые', 'чёрные', 'гуси']

Некорректно метчится:

parser = Parser([G])
(_, tokens), = parser.extract('белый чёрные гуси')
[_.value for _ in tokens]
['белый', 'чёрные', 'гуси']

В правиле

G = Grammar(None, [
    {
        'labels': [
            gram('ADJF')
        ],
        'repeatable': True
    },
    {
        'labels': [
            gram('NOUN')
        ]
    },
    {
        'labels': [
            gram('ADJF')
        ],
        'optional': True
    },
    {
        'labels': [
            gram('NOUN'),
        ],
    }
])

не получится сделать gnc_match между первым вторым NOUN

Find a better way to use InterpretationEngine

Сейчас, для того чтобы извлечь несколько объектов разного типа, нужно создавать для них уникальные InterpretationEngine.
Нужно сделать так, чтобы можно было извлекать объекты через один InterpretationEngine

Exclude unmatched word forms from results

When matching text with gnc-match (or similar labels) it's possible to do some sort of morphological disambiguation solving, for example, with given grammar:

Grammar('Firstname_and_Lastname', [
    {
        'labels': [
            ('gram', 'Name'),
        ],
    },
    {
        'labels: [
            ('gram', 'Surn'),
            ('gnc-match', -1),
        ],
    },
]

Returned matches will contain tokens that have specified grammemes (Name and Surn) with forms attribute that contains all of word forms returned by pymorphy2, but it may be a significant to reduce forms only to that matched labels.

Вывод списков с фактами

Если выводить факты, внутри которых есть списки фактов, то получается вот такой не очень красивый вывод:

image

Последовательный вывод (выше) подходит для списка чисел или строк, например. Но здесь хочется, чтобы Station(...) всё время начинались с новой строки с отступом:

StationAndTransfer(station=Station(name='Спасская',
                                   num=[]),
                   transfer=[Station(name='Садовая',
                                     num=[]),
                     ——————> Station(name='Сенная площадь',
                                     num=[])])

Если нет возможности или желания такое делать, я могу попробовать сделать пул реквест, правда, будет хорошо, если подскажете, в какие файлы смотреть.

Реализовать магические методы для базовых классов

Например, в b88aba6 уже реализован метод __or__ для класса Rule, что дает возможность записать правила так:

A = rule(...)
B = rule(...)
C = (A | B).interpretation(object)

Сейчас такое же правило будет выглядеть так:

A = rule(...)
B = rule(...)
C = or_(A, B).interpretation(object)

Нужно посмотреть, какие методы можно реализовать (например, __and__ для конструкций вида (A & B) == and_(A, B) и __invert__ для ~(A & B) == not_(and_(A, B)))

Парсер для извлечения нескольких типов фактов с интерпретациями

Добрый день!
Пробую использовать yargy для извлечения фактов из текстов на русском языке.
Предположим, что есть 2 типа фактов. Fact1 и Fact2. Написал правила извлечения с интерпретациями этих фактов. Теперь хочу создать один парсер, разбирающий оба типа фактов, например, так:
parser = Parser(or_(fact1, fact2))
Скрипт падает с ошибкой:

TypeError: expected InterpretationNode, got Node

Если же убрать из правил интерпретации, то все нормально отрабатывает (но интерпретаций не получается).
Парсеры для отдельных фактов с интерпретациями работает нормально.

Можно ли вообще в yargy создать один Parser на несколько фактов с интерпретациями? Если да, то каким образом?

Lock() в Parser

C самой первой версии yargy в парсере создаётся и используется Lock https://github.com/natasha/yargy/blob/master/yargy/parser.py#L187 . @dveselov , это точно нужно? Я просто не очень шарю в этой теме. В своей практике вроде бы эта фича мне не пригождалась. Может убрать? Если надо, пользователь может сам создать Lock() при использовании парсера

Refactor labels definition

Instead of passing label names and arguments, we can use decorated labels functions:

grammar = Grammar('Firstname_and_Lastname', [
    {
        'labels': [
            gram('Name'),
        ],
    },
    {
        'labels': [
            gram('Surn'),
            gnc-match(-1),
        ],
    },
]

Cython compilation

setup.zip
Добрый день! Для ускорения работы попробовал собрать вашу библиотеку с помощью cython. Если интересно, то прикрепляю setup.py файл, который использовал для этого.

Собрать не получилось без 2-х небольших правок (возможно это небольшие баги, которые стоит поправить):

  1. В файле interpretation/fact.py отсутствует импорт assert_type из yargy.utils. И это очень похоже на баг =)
  2. В файле rule/constructors.py сборка падала на строчке 196:
    self.rule = rule(item, *items). Тут я не уверен, что это баг, но замена rule на Rule помогла и сборка успешно прошла.

Однако, при попытке использовать полученную собранную либу вылетает ошибка:

C:\Users\alexandr.romanenko\workspace\cython\yargy\parser.pyx in yargy.parser.Parser.init (yargy\parser.c:6233)()

C:\Users\alexandr.romanenko\workspace\cython\yargy\rule\constructors.pyx in yargy.rule.constructors.Rule.normalized (yargy/rule\constructors.c:3524)()

C:\Users\alexandr.romanenko\workspace\cython\yargy\rule\constructors.pyx in yargy.rule.constructors.Rule.transform (yargy/rule\constructors.c:3311)()

C:\Users\alexandr.romanenko\workspace\cython\yargy\visitor.pyx in yargy.visitor.TransformatorsComposition.call (yargy\visitor.c:1910)()

C:\Users\alexandr.romanenko\workspace\cython\yargy\rule\transformators.pyx in yargy.rule.transformators.RuleTransformator.call (yargy/rule\transformators.c:1531)()

C:\Users\alexandr.romanenko\workspace\cython\yargy\rule\constructors.pyx in genexpr (yargy/rule\constructors.c:4227)()

ValueError: generator already executing

Ошибка возникает при попытке создать экземпляр класса Parser. При этом если пользоваться обычной python версией библиотеки, все работает и создается корректно

Понимаю, что вопрос довольно абстрактный, но возможно вы подскажите, что я сделал не так?
Возможно, правка 2 была не верна, и чтобы собрать yargy под cython нужны другие (возможно, более глобальные) исправления кода. Или может быть по этому небольшому сообщению об ошибке Вы все-таки поймете, в чем проблема...
Все-таки получить 30% ускорения работы библиотеки просто за счет сборки cython - это очень заманчиво =)

И еще вопрос: на данный момент pip install yargy устанавливает версию 0.8.0 библиотеки. Я правильно понимаю, что нужно устанавливать библиотеку прямо из github, забирая последнюю версию (0.9.1), потому как все примеры и туториалы на 0.8.0 не работают...

crf-теггер

Здравствуйте. Есть какая-то инструкция по обучение crf-теггера или хотя бы справка, интересует вопрос дообучения теггера имен.

Railroad diagrams for grammars

Грамматики можно показывать как на http://json.org/ . Например

REGION_CITY_SHORT_GRAMMAR = grammar(
    'г',
    '.',
    is_capitalized(True)
)

REGION_TYPE_BEFORE_GRAMMAR = grammar(
    gram('Geo/Type'),
    is_capitalized(True)
)

REGION_TYPE_AFTER_GRAMMAR = grammar(
    is_capitalized(True),
    gram('Geo/Type')
)

REGION_GRAMMAR = or_(
    REGION_CITY_SHORT_GRAMMAR,
    REGION_TYPE_BEFORE_GRAMMAR,
    REGION_TYPE_AFTER_GRAMMAR
)

image

TITLE_GRAMMAR = grammar(
    gram('L-QUOTE'),
    not_(gram('QUOTE')).repeatable(),
    gram('R-QUOTE'),
)

TYPE_TITLE = grammar(
    gram('Item/Type'),
    TITLE_GRAMMAR
)

image

Есть библиотека https://github.com/tabatkins/railroad-diagrams с реализаций на Питоне. Будет полезно при работе в Jupyter для отладки. Можно делать супер документацию для Наташи

[RFC] DSL for grammars

Хотелось бы сделать запись грамматик более компактной и однообразной. Примеры:
Было

SUD_GRAMMAR = Grammar('sud', [
    {
        'labels': [
            is_capitalized(True)
        ]
    },
    {
        'labels': [
            dictionary(SUD_REGIONALITY)
        ],
        'optional': True
    },
    {
        'labels': [
            dictionary({'суд'})
        ]
    }
])

Стало

SUD_GRAMMAR = grammar(
    is_capitalized(True),
    dictionary(SUD_REGIONALITY).optional(),
    dictionary({'суд'})
)

REGION_CITY_SHORT_GRAMMAR = [
    {
        'labels': [
            eq('г')
        ]
    },
    {
        'labels': [
            eq('.')
        ]
    },
    {
        'labels': [
            is_capitalized(True)
        ]
    },
]
REGION_CITY_SHORT_GRAMMAR = grammar(
    'г',
    '.',
    is_capitalized(True)
)

Latin = [
        {
            'labels': [
                gram('LATN'),
                is_capitalized(True),
            ],
            'repeatable': True,
            'normalization': NormalizationType.Original,
            'interpretation': {
                'attribute': [
                    OrganisationObject.Attributes.Name,
                ]
            },
        },
        {
            'labels': [
                gram('INT'),
            ],
            'optional': True,
            'normalization': NormalizationType.Original,
            'interpretation': {
                'attribute': [
                    OrganisationObject.Attributes.Name,
                ]
            },
        }
    ]
Latin = grammar(
    and_(
        gram('LATN'),
        is_capitalized(True),
    ).repeatable().interpretation(
        OrganisationObject.Attributes.Name,
        normalization=NormalizationType.Original
    ),
    gram('INT').optional().interpretation(
        OrganisationObject.Attributes.Name,
        normalization=NormalizationType.Original
    )
)

То есть предлагается изменить форму записи. Содержание останется прежним. Новую запись легко автоматически транслировать в старую. А старую, если постараться, в новую. Основные моменты:

Не использовать dict и partial для правил

Так покороче. В строке repetable, например, можно опечататься. В IDE наверное работает комплишн. Нормальный repr или даже визуализации #17 помогут в отладке. Методы .optional, .repeatable, .interpretation в будущем могут появиться и у грамматик.

 {
    'labels': [
        gram('ADJF'),
        # gnc_match(-1, solve_disambiguation=True),
    ],
    'optional': True,
    'repeatable': True,
}
and_(
    gram('ADJF'),
    gnc_match(-1, solve_disambiguation=True),
).optional().repeatable()

Вместо eq(X) писать X

Иногда значительно короче и понятнее

RESHENIE_RULE = {
    'labels': [
        eq('решение')
    ]
}

OT_RULE = {
    'labels': [
        eq('от')
    ]
}

DECISION_GRAMMAR = OR(
    [
        RESHENIE_RULE,
        SUD_GRAMMAR,
        OT_RULE,
        DATE_GRAMMAR
    ],
    [
        RESHENIE_RULE,
        SUD_GRAMMAR,
        REGION_GRAMMAR,
        OT_RULE,
        DATE_GRAMMAR
    ],
)
DECISION_GRAMMAR = or_(
    grammar(
        'решение',
        SUD_GRAMMAR,
        'от',
        DATE_GRAMMAR
    ),
    grammar(
        'решение',
        SUD_GRAMMAR,
        REGION_GRAMMAR,
        'от',
        DATE_GRAMMAR
    ),
)

Использовать not_

not_eq -> not_(eq), length_not_eq -> not_(length_eq) . Теоретически в будущем not_ можно применять и к грамматикам

Использовать or_ и для правил, и для грамматик

OR -> or_. Может быть, использовать or_ вместо enum. Так однообразнее

Над чем ещё можно подумать:
Случаи типа Latin = ...; WithConj = Latin[0]
Имена грамматик и правил. Может быть grammar.name('WithConj')
Примеры для грамматик и правил. Может быть rule.examples('2016 12 01')
Комментарии. grammar.comment('....'). Примеры и комментарии могут быть просто написаны через # в исходниках, но их можно было бы использовать в документации и при отладке

CustomGrammemesPipeline drops tokens

Есть CustomGrammemesPipeline из двух записей

class Pipeline(CustomGrammemesPipeline):
    Grammemes = {
        'Gram',
    }
    Dictionary = {
        'текст',
        'текст_песня',
    }

Для "текст песни" всё нормально

from yargy.tokenizer import Tokenizer
tokenizer = Tokenizer()
tokens = tokenizer.transform('Текст песни музыкальной группы')
tokens = Pipeline()(tokens)
[_.value for _ in tokens]

['Текст_песни', 'музыкальной', 'группы']

Для "текст" не работает:

tokens = tokenizer.transform('Текст')
tokens = Pipeline()(tokens)
[_.value for _ in tokens]

[]

Не проставляет 'Gram':

tokens = tokenizer.transform('Текст АБВ')
tokens = Pipeline()(tokens)
list(tokens)

[Token('Текст', (0, 5), [{'grammemes': {'nomn', 'masc', 'NOUN', 'inan', 'sing'}, 'score': 0.653846, 'methods_stack': ((<DictionaryAnalyzer>, 'текст', 33, 0),), 'normal_form': 'текст'}, {'grammemes': {'accs', 'masc', 'NOUN', 'inan', 'sing'}, 'score': 0.346153, 'methods_stack': ((<DictionaryAnalyzer>, 'текст', 33, 3),), 'normal_form': 'текст'}]),
 Token('АБВ', (6, 9), [{'grammemes': {'UNKN'}, 'score': 1.0, 'methods_stack': ((<UnknAnalyzer>, 'АБВ'),), 'normal_form': 'абв'}])]

Новые пайплайны

Текст тикета не закончен и будет дополняться

С пайплайнами есть несколько проблем:

  1. Бывает забываешь добавить пайплайны при инициализации парсера. Парсер мог бы сам добавить пайплайн или хотя бы кинуть ворнинг если где-то в грамматиках встречается предикат типа gram('CustomGram') , а соответствующего пайплайна нет в аргументах парсера.
  2. Можно ошибиться в предикате gram. Написать например не gram('CustomGram') а gram('Custom'). Такое происходит не из-за опечаток (20 минут искал баг, а оказывается 'OrganizationCrf' != 'OrganisationCrf'), а когда начинаешь менять код в разных местах. В пайплайне заменил grammemes а в грамматиках забыл.
  3. Конфликтующие пайплайны. Например, один пайплайн для названий улиц создаёт мультитокен "Карл Маркс" для "ул. Карла Маркса" , а в пайплайне для фамилий есть "Маркс". Если пайплайн для фамилий идёт после пайплайна для улиц ФИО "Карл Маркс" не заметчится.
  4. Нужно учитывать токенизатор. Нельзя написать "научно-производственное предприятие", нужно ставить пробелы "научно - производственное предприятие"
  5. Нет согласования слов. По-хорошему, "Карлом Марксу" не должно метчиться
  6. Неленивая инициализация. В конструкторе пайплайна вызывается MorphAnalizer. Если пайплайн большой, это занимает время. Хорошо бы эту работу делать не при импорте, а при запуске парсера.

Split by "-" in tokenizer

Сейчас токенайзер не разбивает слова типа "Ростов-на-Дону". Вот причины почему это не оч:

  1. Тяжело поддержать случаи типа "Ростов- на-Дону", "Ростов - на- Дону"
  2. Неожиданно разбиваются на части слова типа "Истра-1" и "dvd-диск"
  3. Бывают разные "-": ‑– —−

Предлагаю разбивать "Ростов-на-Дону" на "Ростов", "-", "на", "-", "Дону"

@dveselov , возражения?

Tokenizer fails to parse integer ranges with big numbers on python 2

Python 2.7.12+ (default, Sep 17 2016, 12:08:02) 
>>> xrange(int('1433'), int('40817810505751201174'))
OverflowError: Python int too large to convert to C long

Python 3.5.2+ (default, Sep 22 2016, 12:18:14) 
>>> range(int('1433'), int('40817810505751201174'))
range(1433, 40817810505751201174)

CustomGrammemesPipeline does not join tokens

Рассмотрим пайплайн из одного правила "b_d". Оно работает правильно. Как и ожидалось токены b и d в строчке "a b d" склеиваются, получается a, b_d:

from yargy.tokenizer import Tokenizer
from yargy.pipeline import CustomGrammemesPipeline


class MyPipeline(CustomGrammemesPipeline):
    Grammemes = {
        'My',
    }
    Dictionary = {
        'b_d'
    }


pipeline = MyPipeline()
    
tokenizer = Tokenizer()
tokens = list(tokenizer.transform('a b d'))
tokens = list(pipeline(tokens))
tokens

[Token('a', (0, 1), [{'grammemes': {'LATN'}, 'normal_form': 'a'}]),
 Token('b_d', (2, 5), [{'grammemes': {'My'}, 'normal_form': 'b_d'}])]

Теперь добавим к b_d ещё одно правило a_b_c. Казалось бы для строчки "a b d" ничего не должно измениться. Но меняется. b и d перестают объединяться:

from yargy.tokenizer import Tokenizer
from yargy.pipeline import CustomGrammemesPipeline


class MyPipeline(CustomGrammemesPipeline):
    Grammemes = {
        'My',
    }
    Dictionary = {
        'a_b_c',
        'b_d'
    }


pipeline = MyPipeline()
    
tokenizer = Tokenizer()
tokens = list(tokenizer.transform('a b d'))
tokens = list(pipeline(tokens))
tokens

[Token('a', (0, 1), [{'grammemes': {'LATN'}, 'normal_form': 'a'}]),
 Token('b', (2, 3), [{'grammemes': {'LATN'}, 'normal_form': 'b'}]),
 Token('d', (4, 5), [{'grammemes': {'ROMN', 'NUMBER', 'LATN'}, 'normal_form': 'd'}])]

По-моему это глобальная проблема CustomGrammemesPipeline. При текущей реализации пайплайнов эту проблему не получится исправить.

Как правильно выкусывать имена объектов?

Привет! Например у меня такие строки:

"Скиньте документы по клиенту Рога и Копыта за август"
или
"Скиньте документы по клиенту Рик за август"
или
"Скиньте акты по клиенту Horns and hooves за август"

С учетом того, что "клиенту" у меня разбирается в ENTITY_G = gram('Entity')
, а "август" разбирается в DATE_G

DATE_G = or_(
    rule(
        DAY.interpretation(Date.day),
        MONTH_NAME.interpretation(Date.month.inflected()),
        YEAR.interpretation(Date.year)
    ),
   ....
).interpretation(
    Date
).named('DATE')

Т.е. имена клиентов могут быть и на русском и на английском и на латышском и могут не обрамляться кавычками.
На крайний случай я могу из БД выбрать все вариации имен клиентов и попробовать пихнуть в Газеттир или типа того, чтобы объединить слова, но желательно бы этого не делать таким образом.

Как лучше сделать?

Вопрос по поводу ё

во первых хочу сказать спасибо за отличную библиотеку!
моя проблема:
я использую свой пайплайн для пометки должностей, если исходное слово содержит е вместо ё, то на выходе я получаю не полный набор граммем, а только мою пометку должности, пример кода:

text = u'партнер'

class TestPipeLine(MorphPipeline):
    grammemes = {
        'Person/Position'
    }
    keys = [
      u'партнер',
    ]

tokenizer = Tokenizer()
stream = tokenizer(text)

pipeline = TestPipeLine()
stream = pipeline(stream)
for token in stream:
  print repr(token.forms).decode('unicode-escape')

результат: [Form(u'партнер', set(['Person/Position']))]

т.е. тут как я понял он не копирует граммемы т.к. нормализованные формы не совпадают 'партнёр' != 'партнер',

без пайплайна, после токнайзера формы такие:
[Form(u'партнёр', set(['masc', 'sing', 'anim', 'nomn', 'NOUN']))]

хотелось бы как-то получить результат с объединенными граммемами set(['masc', 'sing', 'anim', 'nomn', 'NOUN', 'Person/Position'])

не подскажите как можно решить этот вопрос, без модификации кода yargy?

[RFC] Interface to add rules to tokenizer

Предлагаю инициализировать токенайзер списком правил. По умолчанию список будет состоять из RUSSIAN, LATN, QUOTE, INT, PUNCT, END-OF-LINE. Но реализация для EMAIL, PHONE, URL тоже будет и пользователь по желанию сможет их добавить. Нужно явно указать, что Наташа будет работать только со стандартным списком правил.

Правила могут быть оформлены так:

class IntRule(TokenRule):
    pattern = r'[+-]?\d+'
    construct = int
    grammemes = {'NUMBER', 'INT'}

class EmailRule(TokenRule):
    pattern = r'[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+'
    grammemes = {'EMAIL'}

Правила добавляются так:

tokenizer = Tokenizer()
tokenizer.add_rules(
    EmailRule(),
    PhoneRule()
)
parser = Parser(G, tokenizer=tokenizer)

Продвинутый пользователь сможет создать и добавить какое-нибудь необычное правило, которое не вписывается в пайплайн

Speedup pipelines

Current pipelines realization seems to be a quite slow, mostly because they're chained together, as I think.
In this case better approach is use same technique as GLR-parser does - push tokens from tokenizer to pipelines, check that pipelines have matches, if it does - push next tokens, until final match will be found.

Automatically normalise CustomGrammemesPipeline arguments

Например, у меня есть список сложных имён

[
    'Абд Аль-Азиз Бин Мухаммад',
    'Абд ар-Рахман Наср ас-Са ди',
    'Абд ар-Рахман ибн Хасан',
    'Абд-аль Хади ибн Али',
    'Абд-уль-Кадим Заллюм',
    'Абду-ль-Азиз Аль Абдуль-ли-Лятыф',
    'Абдулла Нирша',
    'Абдуль Азиза Ар-Раййиса',
    'Абдульхамида ибн Абдуррахмана ас-Сухайбани',
...

Я хочу их обрабатывать с помощью CustomGrammemesPipeline. Для этого мне нужно переписать имена как-то так

[
    'абд_аля-азиза_бин_мухаммад',
    'абд_ар-рахман_наср_ас-с_ди',
    'абд_ар-рахман_ибн_хасан',
    'абд-аль_хади_ибн_али',
    'абд-уль-кадить_заллюм',
    'абду-ль-азиз_аль_абдуль-ли-лятыф',
    'абдулла_нирша',
    'абдуль_азиз_ар-раййиса',
    'абдульхамид_ибн_абдуррахман_ас-сухайбаня',
    'абдуррахман_раафат_аля-баш',
    'аба_амад_абдуллах_ибн_джамиль',
...

Вручную это сделать нереально. Может быть нормировать строки пользователя внутри CustomGrammemesPipeline автоматически?

Также просто по-дурацки читаются записи типа:

'музыкальный_группа'
'северный_осетия'
'печатный_издание'
'программный_обеспечение'

Фиксировать версии pymorphy2 и pymorphy2_dicts_ru при тестировании

В версии словарей 2.4.404381.4454046, которая у меня и на https://b-labs.pro/natasha/, у 'куценко', tag=OpencorporaTag('NOUN,anim,ms-f,Fixd,Surn plur,loct') в версии 2.4.393658.3725883, которая сейчас в Travis tag=OpencorporaTag('NOUN,anim,GNdr,Ms-f,Fixd,Surn sing,nomn').

У меня ms-f, там Ms-f. У меня тест c Гоша Куценко ломается, там работает.

Нормализация отдает предпочтение мужским именам

При нормализации токена она происходит крайне просто — выбирается самый первый вариант из предложенных.
Такое поведение иногда ошибочно распознает имена, например: "Актриса Ярослава Новожилова" — извлекается {'first': 'ярослав', 'last': 'новожилов'}.

Нужно как-то иначе проводить нормализацию, так чтобы был учет контекста. Скорее всего это потребует более серьезных методов обработки предложения, например составления дерева зависимостей между словами в нем. Либо же это проблема встроенного в PyMorphy CRF-теггера.

repeatable и RecursionError

RULE = rule(
    'a',
    rule('b').repeatable(),
    'c'
)

parser = Parser(RULE)
text = ' '.join(['a'] + ['b'] * 1000 + ['c'])
list(parser.match(text))

RecursionError                            Traceback (most recent call last)
<ipython-input-40-a75887a32205> in <module>()
      1 parser = Parser(RULE)
      2 text = ' '.join(['a'] + ['b'] * 1000 + ['c'])
----> 3 list(parser.match(text))

/Users/alexkuk/natasha/yargy/yargy/parser.py in match(self, text)
    224         # NOTE Not an optimal 
...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.