Natasha

Материал из NLPub
Перейти к навигации Перейти к поиску
Well.svg Данная статья была создана в рамках учебного задания.
Студент: Участник:User:Abiks
Преподаватель: Антон Алексеев
Срок: 10 декабря 2019

В настоящее время задание завершено и проверено. Данная страница может свободно правиться другими участниками NLPub.


natasha — это библиотека для извлечения именованных сущностей на основе Yargy-парсера и CRF, плюс некоторое количество предобученных моделей, например, для извлечения имен.

Установка

Может быть установлена через pip командой

   pip install natasha


Извлечение имен

text = """
Школа анализа данных возникла в 2007 году с целью восполнить 
недостаток квалифицированных кадров на рынке труда, с которым 
столкнулся Яндекс в то время. Научным руководителем ШАД стал 
профессор Ратгерского университета Илья Мучник, директором — Елена Бунина. 
"""

Три вида объектов, извлекающих имена:

NamesExtractor

Сначала вызывается CRF для поиска в тексте подстрок, похожих на имя человека. Потом для этих подстрок вызывается yargy-парсер для разделения на составляющие (имя, фамилия, отчество). Результат его работы возвращается в виде объекта natasha.extractors.Matches, являющегося контейнером для объектов Match. У каждого объекта Match есть основные поля span (начало и конец найденной сущности в тексте) и fact (краткое описание сущности)

from natasha import NamesExtractor
extractor = NamesExtractor()
matches = extractor(text)
for match in matches:
    start, stop = match.span
    print(start, stop, text[start:stop])
    print(match.fact)
    print()

>> 198 209 Илья Мучник
>> Name(first='илья', middle=None, last='мучник', nick=None)
>> 
>> 224 236 Елена Бунина
>> Name(first='елена', middle=None, last='бунина', nick=None)
>>

PersonExtractor

Аналогично NamesExtractor, но также находит должности (position) людей.


from natasha import PersonExtractor
person_extractor = PersonExtractor()
for match in person_extractor(text):
    start, stop = match.span
    print(start, stop, text[start:stop])
    print(match.fact)
    print()

>> 164 181 руководителем ШАД
>> Person(position='руководителем', name=Name(first=None, middle=None, last='шад', nick=None))
>> 
>> 198 209 Илья Мучник
>> Person(position=None, name=Name(first='илья', middle=None, last='мучник', nick=None))
>> 
>> 224 236 Елена Бунина
>> Person(position=None, name=Name(first='елена', middle=None, last='бунина', nick=None))
>>

SimpleNamesExtractor

По сравнению с предыдущими эксракторами у SimpleNamesExtractor слишком много ложных срабатываний:

for match in simple_extractor(text):
    print(match.fact)
    
>> Name(first=None, middle=None, last='данный', nick=None)
>> Name(first=None, middle=None, last='возникнувшая', nick=None)
>> Name(first=None, middle=None, last='в', nick=None)
>> Name(first=None, middle=None, last='с', nick=None)
>> Name(first=None, middle=None, last='цель', nick=None)
>> Name(first=None, middle=None, last='кадр', nick=None)
>> Name(first=None, middle=None, last='рынок', nick=None)
>> Name(first=None, middle=None, last='труд', nick=None)
>> Name(first=None, middle=None, last='с', nick=None)
>> Name(first=None, middle=None, last='яндекс', nick=None)
>> Name(first=None, middle=None, last='в', nick=None)
>> Name(first=None, middle=None, last='то', nick=None)
>> Name(first=None, middle=None, last='шад', nick=None)
>> Name(first=None, middle=None, last='ставший', nick=None)
>> Name(first='илья', middle=None, last='мучник', nick=None)
>> Name(first=None, middle=None, last='директор', nick=None)
>> Name(first='елена', middle=None, last='бунина', nick=None)


Это происходит из-за того, что данный класс при разборе строки не вызывает CRF-теггер, только парсит строку с помощью правил. А имеющиеся готовые правила на основе Yargy-парсера предназначены только для разбора уже найденной с помощью CRF-теггера строки, и не годятся для обработки сырых текстов (кстати, имена "Илья Мучник" и "Елена Бунина" экстрактор нашел правильно). Авторы natasha рекомендуют его вызывать только для тех строк, для которых априори известно, что они содержат только имена:


 from natasha import SimpleNamesExtractor
 simple_extractor = SimpleNamesExtractor()
 for match in simple_extractor('профессор Илья Мучник'):
   print(match.fact)
 
>> Name(first='илья', middle=None, last='мучник', nick=None)

Извлечение адресов и названий географических объектов

Для этого в библиотеке имеются два класса: LocationExtractor и AddressExtractor. Первый -- для извлечения объектов вроде "Швейцария" или "Академгородок". Второй -- для извлечения адресов, например, "ул. Московская д.189".

Покажем, как оба экстрактора отрабатывают на одном и том же тексте:


text = """Сегодня я поехал с аэропорта Толмачево в Академгородок -- 
мне нужно было привести посылку из Бостона, США. 
Мы ехали по Димитровскому мосту, потом свернули на улицу Большевицкую, 
ехали по ней. Остановились в кафе "хан-бууз", что находится по адресу Старое Шоссе 67А. 
Потом поехали на ул.Николаева 11, заехав по пути на Ляпунова 3"""


AddressExtractor

from natasha import AddressExtractor
address_extractor = AddressExtractor()

for match in address_extractor(text):
    print(match.fact)
    
>> Address(parts=[Street(name='Старое', type='шоссе'), Building(number='67А', type=None)])
>> Address(parts=[Street(name='Николаева', type='улица'), Building(number='11', type=None)])


Стоит отметить, что AddressExtractor сильно требователен к контексту. По этой причине он увидел второй адрес ("Николаева 11") только из-за сокращения "ул." перед ним (в первом адресе он неверно определил название улицы, должно было быть "Старое Шоссе", а не шоссе "Старое"). И поскольку у третьего адреса в тексте не было никаких явных указаний на то, что это адрес, natasha его не распознала.

Однако, у данной библиотеки неплохо работает парсер найденных адресов, разделяя название улицы и номер дома. Также, если указать название города, Наташа его тоже добавит в адрес:

for match in address_extractor('г.Новосибирск ул.Ленина 52'):
    print(match.fact) 

>> Address(parts=[Settlement(name='Новосибирск', type='город'), Street(name='Ленина', type='улица'), Building(number='52', type=None)])


LocationExtractor

from natasha import LocationExtractor
location_extractor = LocationExtractor()

for match in location_extractor(text):
    print(match.fact)
   
>> Location(name='толмачево')
>> Location(name='академгородок')
>> Location(name='сша')
>> Location(name='николаев')

Этот экстрактор возвращает более простые ответы: объекты Location с только одним полем name, причем выполняет нормализацию, поскольку в отличие от AddressExtractor (использующего связку CRF-tagger + Yargy-parser) работает исключительно на основе yargy-парсера.

Вот еще один пример их сравнения:


for match in location_extractor("Россия, Москва, ул.Арбат д. 14"):
    print(match.fact)
>> Location(name='россия')
>> Location(name='москва')
>> Location(name='арбат')


for match in address_extractor("Россия, Москва, ул.Арбат д. 14"):
    print(match.fact)
   
>> Address(parts=[Country(name='Россия'), Settlement(name='Москва', type=None), Street(name='Арбат', type='улица'), Building(number='14', type='дом')])

Ссылки