Natasha solves basic NLP tasks for Russian language: tokenization, sentence segmentatoin, word embedding, morphology tagging, lemmatization, phrase normalization, syntax parsing, NER tagging, fact extraction.
Library is just a wrapper for lower level tools from Natasha project:
Consider using these lower level tools for realword tasks. Natasha models are optimized for news articles, on other domains quality may be worse.
from natasha import (
Segmenter,
MorphVocab,
NewsEmbedding,
NewsMorphTagger,
NewsSyntaxParser,
NewsNERTagger,
PER,
NamesExtractor,
DatesExtractor,
MoneyExtractor,
AddrExtractor,
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)
dates_extractor = DatesExtractor(morph_vocab)
money_extractor = MoneyExtractor(morph_vocab)
addr_extractor = AddrExtractor(morph_vocab)
Doc
aggregates annotators, initially it has just text
field defined:
text = 'Посол Израиля на Украине Йоэль Лион признался, что пришел в шок, узнав о решении властей Львовской области объявить 2019 год годом лидера запрещенной в России Организации украинских националистов (ОУН) Степана Бандеры. Свое заявление он разместил в Twitter. «Я не могу понять, как прославление тех, кто непосредственно принимал участие в ужасных антисемитских преступлениях, помогает бороться с антисемитизмом и ксенофобией. Украина не должна забывать о преступлениях, совершенных против украинских евреев, и никоим образом не отмечать их через почитание их исполнителей», — написал дипломат. 11 декабря Львовский областной совет принял решение провозгласить 2019 год в регионе годом Степана Бандеры в связи с празднованием 110-летия со дня рождения лидера ОУН (Бандера родился 1 января 1909 года). В июле аналогичное решение принял Житомирский областной совет. В начале месяца с предложением к президенту страны Петру Порошенко вернуть Бандере звание Героя Украины обратились депутаты Верховной Рады. Парламентарии уверены, что признание Бандеры национальным героем поможет в борьбе с подрывной деятельностью против Украины в информационном поле, а также остановит «распространение мифов, созданных российской пропагандой». Степан Бандера (1909-1959) был одним из лидеров Организации украинских националистов, выступающей за создание независимого государства на территориях с украиноязычным населением. В 2010 году в период президентства Виктора Ющенко Бандера был посмертно признан Героем Украины, однако впоследствии это решение было отменено судом. '
doc = Doc(text)
doc
Doc(text='Посол Израиля на Украине Йоэль Лион признался, чт...)
After applying segmenter
two new fields appear sents
and tokens
:
doc.segment(segmenter)
display(doc)
display(doc.sents[:2])
display(doc.tokens[:5])
Doc(text='Посол Израиля на Украине Йоэль Лион признался, чт..., tokens=[...], sents=[...])
[DocSent(stop=218, text='Посол Израиля на Украине Йоэль Лион признался, чт..., tokens=[...]), DocSent(start=219, stop=257, text='Свое заявление он разместил в Twitter.', tokens=[...])]
[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='Йоэль')]
After applying morph_tagger
and syntax_parser
, tokens get 5 new fields id
, pos
, feats
, head_id
, rel
— annotation in Universal Dependencies format:
doc.tag_morph(morph_tagger)
doc.parse_syntax(syntax_parser)
display(doc.tokens[:5])
[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>)]
After applying ner_tagger
doc gets spans
field with PER, LOC, ORG annotation:
doc.tag_ner(ner_tagger)
display(doc.spans[:5])
[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=[...])]
Natasha wraps Ipymarkup to provide ASCII visualizations for morphology, syntax and NER annotations. doc
and sents
have 3 methods: morph.print()
, syntax.print()
and ner.print()
:
doc.ner.print()
Посол Израиля на Украине Йоэль Лион признался, что пришел в шок, узнав 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──── было отменено судом.
sent = doc.sents[0]
sent.morph.print()
Посол 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 пришел VERB|Aspect=Perf|Gender=Masc|Mood=Ind|Number=Sing|Tense=Past|VerbForm=Fin|Voice=Act в ADP шок NOUN|Animacy=Inan|Case=Acc|Gender=Masc|Number=Sing , PUNCT узнав VERB|Aspect=Perf|Tense=Past|VerbForm=Conv|Voice=Act о ADP решении NOUN|Animacy=Inan|Case=Loc|Gender=Neut|Number=Sing властей NOUN|Animacy=Inan|Case=Gen|Gender=Fem|Number=Plur Львовской ADJ|Case=Gen|Degree=Pos|Gender=Fem|Number=Sing области NOUN|Animacy=Inan|Case=Gen|Gender=Fem|Number=Sing объявить VERB|Aspect=Perf|VerbForm=Inf|Voice=Act 2019 ADJ год NOUN|Animacy=Inan|Case=Acc|Gender=Masc|Number=Sing годом NOUN|Animacy=Inan|Case=Ins|Gender=Masc|Number=Sing лидера NOUN|Animacy=Anim|Case=Gen|Gender=Masc|Number=Sing запрещенной VERB|Aspect=Perf|Case=Gen|Gender=Fem|Number=Sing|Tense=Past|VerbForm=Part|Voice=Pass в ADP России PROPN|Animacy=Inan|Case=Loc|Gender=Fem|Number=Sing Организации PROPN|Animacy=Inan|Case=Gen|Gender=Fem|Number=Sing украинских ADJ|Case=Gen|Degree=Pos|Number=Plur националистов NOUN|Animacy=Anim|Case=Gen|Gender=Masc|Number=Plur ( PUNCT ОУН PROPN|Animacy=Inan|Case=Nom|Gender=Fem|Number=Sing ) PUNCT Степана PROPN|Animacy=Anim|Case=Gen|Gender=Masc|Number=Sing Бандеры PROPN|Animacy=Anim|Case=Gen|Gender=Masc|Number=Sing . PUNCT
sent.syntax.print()
┌──► Посол 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
Tokens have lemmatize
method, it uses pos
and feats
assigned by morph_tagger
to get word normal form. morph_vocab
is just a wrapper for Pymorphy2:
for token in doc.tokens:
token.lemmatize(morph_vocab)
{_.text: _.lemma for _ in doc.tokens[:10]}
{'Посол': 'посол', 'Израиля': 'израиль', 'на': 'на', 'Украине': 'украина', 'Йоэль': 'йоэль', 'Лион': 'лион', 'признался': 'признаться', ',': ',', 'что': 'что', 'пришел': 'прийти'}
Consider phrase "Организации украинских националистов", one can not just inflect every word independently to get normal form: "Организация украинский националист". Spans have method normalize
that uses syntax annotation by syntax_parser
to inflect phrases:
for span in doc.spans:
span.normalize(morph_vocab)
{_.text: _.normal for _ in doc.spans}
{'Израиля': 'Израиль', 'Украине': 'Украина', 'Йоэль Лион': 'Йоэль Лион', 'Львовской области': 'Львовская область', 'России': 'Россия', 'Организации украинских националистов (ОУН)': 'Организация украинских националистов (ОУН)', 'Степана Бандеры': 'Степан Бандера', 'Twitter': 'Twitter', 'Украина': 'Украина', 'Львовский областной совет': 'Львовский областной совет', 'ОУН': 'ОУН', 'Житомирский областной совет': 'Житомирский областной совет', 'Петру Порошенко': 'Петр Порошенко', 'Бандере': 'Бандера', 'Украины': 'Украина', 'Верховной Рады': 'Верховная Рада', 'Бандеры': 'Бандера', 'Степан Бандера': 'Степан Бандера', 'Организации украинских националистов': 'Организация украинских националистов', 'Виктора Ющенко': 'Виктор Ющенко', 'Бандера': 'Бандера'}
To split names like "Виктор Ющенко", "Бандера" and "Йоэль Лион" into parts use names_extractor
and spans method extract_fact
:
for span in doc.spans:
if span.type == PER:
span.extract_fact(names_extractor)
{_.normal: _.fact.as_dict for _ in doc.spans if _.fact}
{'Йоэль Лион': {'first': 'Йоэль', 'last': 'Лион'}, 'Степан Бандера': {'first': 'Степан', 'last': 'Бандера'}, 'Петр Порошенко': {'first': 'Петр', 'last': 'Порошенко'}, 'Бандера': {'last': 'Бандера'}, 'Виктор Ющенко': {'first': 'Виктор', 'last': 'Ющенко'}}
One may use Natasha components independently. It is not mandatory to construct Doc
object.
Segmenter
¶Segmenter
is just a wrapper for Razdel, it has two methods tokenize
and sentenize
:
tokens = list(segmenter.tokenize('Кружка-термос на 0.5л (50/64 см³, 516;...)'))
for token in tokens[:5]:
print(token)
Token(start=0, stop=13, text='Кружка-термос') Token(start=14, stop=16, text='на') Token(start=17, stop=20, text='0.5') Token(start=20, stop=21, text='л') Token(start=22, stop=23, text='(')
text = '''
- "Так в чем же дело?" - "Не ра-ду-ют".
И т. д. и т. п. В общем, вся газета
'''
sents = list(segmenter.sentenize(text))
for sent in sents:
print(sent)
Sent(start=1, stop=23, text='- "Так в чем же дело?"') Sent(start=24, stop=40, text='- "Не ра-ду-ют".') Sent(start=42, stop=57, text='И т. д. и т. п.') Sent(start=58, stop=77, text='В общем, вся газета')
MorphVocab
¶MorphVocab
is a wrapper for Pymorphy2. MorphVocab
adds cache and adapts grammems to Universal Dependencies format:
forms = morph_vocab('стали')
forms
[MorphForm(normal='стать', pos='VERB', feats={'VerbForm': 'Fin', 'Aspect': 'Perf', 'Number': 'Plur', 'Tense': 'Past', 'Mood': 'Ind'}), MorphForm(normal='сталь', pos='NOUN', feats={'Animacy': 'Inan', 'Gender': 'Fem', 'Number': 'Sing', 'Case': 'Gen'}), MorphForm(normal='сталь', pos='NOUN', feats={'Animacy': 'Inan', 'Gender': 'Fem', 'Number': 'Plur', 'Case': 'Nom'}), MorphForm(normal='сталь', pos='NOUN', feats={'Animacy': 'Inan', 'Gender': 'Fem', 'Number': 'Sing', 'Case': 'Dat'}), MorphForm(normal='сталь', pos='NOUN', feats={'Animacy': 'Inan', 'Gender': 'Fem', 'Number': 'Sing', 'Case': 'Loc'}), MorphForm(normal='сталь', pos='NOUN', feats={'Animacy': 'Inan', 'Gender': 'Fem', 'Number': 'Plur', 'Case': 'Acc'})]
morph_vocab.__call__.cache_info()
CacheInfo(hits=214, misses=635, maxsize=10000, currsize=635)
Also MorphVocab
adds method lemmatize
. Given pos
and feats
it selects the most suitable morph form and returns its normal
field:
morph_vocab.lemmatize('стали', 'VERB', {})
'стать'
morph_vocab.lemmatize('стали', 'X', {'Case': 'Gen'})
'сталь'
Embedding
¶Embedding
is a wrapper for Navec — compact pretrained word embeddings for Russian language:
print('Words in vocab + 2 for pad and unk: %d' % len(emb.vocab.words) )
Words in vocab + 2 for pad and unk: 250002
emb['навек'][:10]
array([ 0.3309305 , 0.18249014, 0.23347412, 0.14935994, -0.17402406, -0.47864223, -0.24524143, 0.15673256, -0.08669729, -0.11727095], dtype=float32)
MorphTagger
¶MorphTagger
wraps Slovnet morphology tagger. Tagger has list of words as input and returns markup object. Markup has print
method that outputs morph tags ASCII visualization:
words = ['Европейский', 'союз', 'добавил', 'в', 'санкционный', 'список', 'девять', 'политических', 'деятелей']
markup = morph_tagger(words)
markup.print()
Европейский ADJ|Case=Nom|Degree=Pos|Gender=Masc|Number=Sing союз NOUN|Animacy=Inan|Case=Nom|Gender=Masc|Number=Sing добавил VERB|Aspect=Perf|Gender=Masc|Mood=Ind|Number=Sing|Tense=Past|VerbForm=Fin|Voice=Act в ADP санкционный ADJ|Animacy=Inan|Case=Acc|Degree=Pos|Gender=Masc|Number=Sing список NOUN|Animacy=Inan|Case=Acc|Gender=Masc|Number=Sing девять NUM|Case=Nom политических ADJ|Case=Gen|Degree=Pos|Number=Plur деятелей NOUN|Animacy=Anim|Case=Gen|Gender=Masc|Number=Plur
SyntaxParser
¶SyntaxParser
wraps Slovnet syntax parser. Interface is similar to MorphTagger
:
words = ['Европейский', 'союз', 'добавил', 'в', 'санкционный', 'список', 'девять', 'политических', 'деятелей']
markup = syntax_parser(words)
markup.print()
┌► Европейский amod ┌►└─ союз nsubj ┌─└─── добавил │ ┌──► в case │ │ ┌► санкционный amod ┌─└►└─└─ список obl │ ┌──► девять nummod:gov │ │ ┌► политических amod └──►└─└─ деятелей nmod
NERTagger
¶NERTagger
wraps Slovnet NER tagger. Interface is similar to MorphTagger
but has untokenized text as input:
text = 'Посол Израиля на Украине Йоэль Лион признался, что пришел в шок, узнав о решении властей Львовской области объявить 2019 год годом лидера запрещенной в России Организации украинских националистов (ОУН) Степана Бандеры. Свое заявление он разместил в Twitter. 11 декабря Львовский областной совет принял решение провозгласить 2019 год в регионе годом Степана Бандеры в связи с празднованием 110-летия со дня рождения лидера ОУН (Бандера родился 1 января 1909 года).'
markup = ner_tagger(text)
markup.print()
Посол Израиля на Украине Йоэль Лион признался, что пришел в шок, узнав LOC──── LOC──── PER─────── о решении властей Львовской области объявить 2019 год годом лидера LOC────────────── запрещенной в России Организации украинских националистов (ОУН) LOC─── ORG─────────────────────────────────────── Степана Бандеры. Свое заявление он разместил в Twitter. 11 декабря PER──────────── ORG──── Львовский областной совет принял решение провозгласить 2019 год в ORG────────────────────── регионе годом Степана Бандеры в связи с празднованием 110-летия со дня PER──────────── рождения лидера ОУН (Бандера родился 1 января 1909 года). ORG
Extractor
¶In addition to names_extractor
Natasha bundles several other extractors: dates_extractor
, money_extractor
and addr_extractor
. All extractors are based on Yargy-parser, meaning that they work correctly only on small predefined set of texts. For realword tasks consider writing your own grammar, see Yargy docs for more.
DatesExtractor
¶text = '24.01.2017, 2015 год, 2014 г, 1 апреля, май 2017 г., 9 мая 2017 года'
list(dates_extractor(text))
[Match( start=0, stop=10, fact=Date( year=2017, month=1, day=24 ) ), Match( start=12, stop=20, fact=Date( year=2015, month=None, day=None ) ), Match( start=22, stop=28, fact=Date( year=2014, month=None, day=None ) ), Match( start=30, stop=38, fact=Date( year=None, month=4, day=1 ) ), Match( start=40, stop=51, fact=Date( year=2017, month=5, day=None ) ), Match( start=53, stop=68, fact=Date( year=2017, month=5, day=9 ) )]
MoneyExtractor
¶text = '1 599 059, 38 Евро, 420 долларов, 20 млн руб, 20 т. р., 881 913 (Восемьсот восемьдесят одна тысяча девятьсот тринадцать) руб. 98 коп.'
list(money_extractor(text))
[Match( start=0, stop=18, fact=Money( amount=1599059.38, currency='EUR' ) ), Match( start=20, stop=32, fact=Money( amount=420, currency='USD' ) ), Match( start=34, stop=44, fact=Money( amount=20000000, currency='RUB' ) ), Match( start=46, stop=54, fact=Money( amount=20000, currency='RUB' ) ), Match( start=56, stop=133, fact=Money( amount=881913.98, currency='RUB' ) )]
NamesExtractor
¶names_extractor
should be applied only to spans of text. To extract single fact use method find
:
lines = [
'Мустафа Джемилев',
'О. Дерипаска',
'Фёдор Иванович Шаляпин',
'Янукович'
]
for line in lines:
display(names_extractor.find(line))
Match( start=0, stop=16, fact=Name( first='Мустафа', last='Джемилев', middle=None ) )
Match( start=0, stop=1, fact=Name( first=None, last='О', middle=None ) )
Match( start=0, stop=22, fact=Name( first='Фёдор', last='Шаляпин', middle='Иванович' ) )
Match( start=0, stop=8, fact=Name( first=None, last='Янукович', middle=None ) )
AddrExtractor
¶lines = [
'Россия, Вологодская обл. г. Череповец, пр.Победы 93 б',
'692909, РФ, Приморский край, г. Находка, ул. Добролюбова, 18',
'ул. Народного Ополчения д. 9к.3'
]
for line in lines:
display(addr_extractor.find(line))
Match( start=0, stop=48, fact=Addr( parts=[AddrPart( value='Россия', type='страна' ), AddrPart( value='Вологодская', type='область' ), AddrPart( value='Череповец', type='город' ), AddrPart( value='Победы', type='проспект' )] ) )
Match( start=0, stop=56, fact=Addr( parts=[AddrPart( value='692909', type='индекс' ), AddrPart( value='РФ', type='страна' ), AddrPart( value='Приморский', type='край' ), AddrPart( value='Находка', type='город' ), AddrPart( value='Добролюбова', type='улица' )] ) )
Match( start=0, stop=29, fact=Addr( parts=[AddrPart( value='Народного Ополчения', type='улица' ), AddrPart( value='9к', type='дом' )] ) )