Постановка задачи

Как известно, один и тот же адрес можно написать различными текстовыми способами, используя сокращения, перестановку, вариации наименований и т.п. Встаёт вопрос: существует ли процедура нормализации, отождествляющая реально одинаковые и по-разному записанные адреса?

Ответ положительный, чему и посвящена данная статья.

Какие средства в принципе есть для решения задачи? Их сейчас два: выделение именованных сущностей (NER) и объекты ГАР ФИАС. NER даёт разбиение на адресные элементы и их нормализацию, ГАР ФИАС может дать уникальные идентификаторы. Задача решается, если в качестве нормализации взять множество строк возможных нормализаций наименований элементов, добавив к ним GUID-идентификаторы ГАР, если получится. Два адреса эквивалентны, если хотя бы одна строка из множеств таких их строк совпадает.

А одними объектами ГАР ФИАС можно обойтись, используя только их идентификаторы? Конечно, нет. Во-первых, это не полный классификатор, особенно в части помещений и строений, хотя и постоянно пополняемый. Во-вторых, в адресах бывают специфические элементы, которые в ГАР отсутствуют (например, Московская область, Можайский район, примерно в 0,1 км по направлению на юг от ориентира середина д.Бараново, или пересечение улиц).

Итак, утверждается, что невозможно обойтись для адреса только одной нормализованной строкой для отождествления в общем случае. Но если таких строк будет несколько, причём сформированных определённым образом, то отождествление будет с очень высокой вероятностью.

Нормализация имён улиц

Если бы наименования адресных элементов писались одинаково, то проблемы бы наполовину не было. И дело даже не в ошибках, которые тоже возможны, а в сокращениях и вариативности. Рассмотрим примеры корректных названий: ул. Жукова, ул. имени Жукова, ул. М.Жукова, ул. Марш.Жукова, ул. Жукова Маршала, ул. Имени Четырежды Героя Советского Союза Жукова Георгия Константиновича (это реальный пример, а не шутка, когда в имени уместилась краткая биография) и т.д. Как бы сделать так, чтобы их отождествить...

Для нормализации я использую Pullenti, который сам же и разрабатываю. Там при извлечении названий улиц используются следующие основные принципы:

  • Наименование в общем случае состоит из вариантов текстовых значений и возможного номера. Например, "ул. Восьмого Марта" - это 8 и "МАРТА", "2-я ул. Соколиной горы" - это 2 и "СОКОЛИНОЙ ГОРЫ" + "СОКОЛИНАЯ ГОРА" + "ГОРЫ СОКОЛИНОЙ" + "ГОРА СОКОЛИНАЯ", "ул.10-я" - только номер 10. Всякие летия и годовщины тоже переводятся в одно число. Километры транслируются в числа с суффиксом "км" ("на 59 км автодороги Санкт-Петербург - Псков").

  • Некоторые слова просто отбрасываются: "имени", "им.", "герой Советского Союза/России" и т.п.

  • Для строкового значения формируется максимальное число возможных вариантов путём как перестановки слов, если их несколько, так и вариацией морфологических окончаний. Например, "на ул. Гороховой" непонятно, это ул. Гороховая, или ул. имени Гороховой (фамилия), поэтому формируются оба варианта: "ГОРОХОВОЙ" + "ГОРОХОВАЯ".

  • Учитываются стандартные прилагательные (МАЛЫЙ, ВЕРХНИЙ, НОВЫЙ и т.п.) и их возможные сокращения, они всегда помещаются на первое место.

  • Если имя состоит из 2-х слов, одно из которых имя, профессия или должность, а второе похоже на фамилию, то один из вариантов всегда должен быть этим вторым словом. Например, для "ул. Маршала Жукова" это "ЖУКОВА" + "МАРШАЛА ЖУКОВА" + "ЖУКОВА МАРШАЛА".

  • Некоторые типы сокращений как разворачиваются, так и оставляются в исходном виде. Например, "ул. М.Жукова" - здесь сокращение может быть "МАЛАЯ", "МИХАИЛА", "МАРШАЛА" или что-либо ещё, заранее мы не знаем. Поэтому закладываем "МАЛАЯ ЖУКОВА", "М.ЖУКОВА", "ЖУКОВА".

Все эти эвристики направлены на то, чтобы на выходе получить максимальное число возможных нормализованных вариантов, по которым впоследствии можно производить сравнение. Теперь понятно, как могут быть отождествлены улицы из начала раздела: у всех них будет общая комбинация - "ЖУКОВА".

Не только названия обладают вариативностью, но и типы. Например, "Измайловский пр." - это проезд или проспект? Оба типа возможны.

Итак, две улицы одинаковы, если у них совпадает тип, хотя бы одна из строк (если есть) и номера (если есть).

Структура адреса

Адрес представляет собой последовательность адресных элементов (далее, АЭ). В принципе, в произвольном порядке, хотя обычно начинают с высокого уровня (страны, региона), заканчивая низким (дома, квартиры). Приведём типизацию АЭ по уровням:

  • Страна;

  • Регион: край, область, города Москва, Санкт-Петербург, Севастополь (в ГАР уровень 1);

  • Административный район (в ГАР уровень 2);

  • Муниципальный район: городской\муниципальный округ (в ГАР уровень 3, но в иерархии всегда принадлежит региону);

  • Городское\сельское поселение, внутригородской район, межселенная территория (в ГАР уровень 4, всегда принадлежит муниципальному району);

  • Город (в ГАР уровень 5);

  • Населённый пункт: село, посёлок, деревня (в ГАР уровень 6);

  • Элемент планировочной структуры: кварталы, зоны, массивы, территории садовых товариществ, организаций и т.п. (в ГАР уровень 7);

  • Элемент улично-дорожной сети (в ГАР уровень 8);

  • Земельный участок (в ГАР уровень 9);

  • Здание, сооружение (в ГАР уровень 10);

  • Относительный указатель (например, "Забайкальский край край, Александрово-Заводский р-н., ориентир 6100 метров на юго-запад от с.Онон-Борзя", в ГАР отсутствует);

  • Помещение, квартира, комната (в ГАР уровень 11);

  • Машиноместо (в ГАР уровень 17);

Предположим, что мы умеем отождествлять АЭ каждого из уровней. Чтобы отождествить два адреса, нужно упорядочить АЭ в каждом из адресов и произвести последовательное сравнение. Если самые нижние по уровню АЭ совпадут, то адреса эквивалентны.

Здесь проблема в том, что некоторые АЭ в иерархии могут отсутствовать или наоборот. Например, в ГАР ФИАС в общем случае 2 типа иерархии - по административным районам и по муниципальным районам, и один АЭ может в принципе иметь 2-х разных родителей. Например, "Дагестан район Агульский село Амух" и "Дагестан муниципальный район Агульский сельское поселение сельсовет Амухский село Амух" - один и тот же АЭ в разрезе этих двух иерархий. А если в регионе село с таким именем уникально, то районы могут вообще не указывать: "Дагестан село Амух". Таким образом, для тождественности АЭ должны не только совпадать сами по себе (по типам и наименованиями с вариациями), но и должно учитываться совпадение родительских АЭ вверх по иерархии.

Нормализация других объектов

Выше были описаны принципы нормализации улиц, то есть объектов уровня 8 в терминологии ГАР. Для имён вышележащих объектов используются похожие принципы: также формируются варианты, убираются лишние слова, нормализуются числа. Например, для "поселение Михайловский" (именно так правильно!) нужно "МИХАЙЛОВСКИЙ", "МИХАЙЛОВСКОЕ", "МИХАЙЛОВСКАЯ" (это уже перестраховываемся на всякий случай). Имена также слегка корректируются - убираются твёрдый и мягкий знаки, Ё переводится в Е, две одинаковые рядом буквы - в одну. В дальнейшем сравнение идёт с учётом возможного несовпадения одной буквы и т.д.

Для домов уровня 10 нормализация совсем другая. Здесь номер может в общем случае состоять из трёх элементов:

  1. Номер дома \ владения \ домовладения;

  2. Номер корпуса;

  3. Номер строения \ сооружения \ литера;

Причём номер - это не только цифры, но и дроби, буквы и их комбинации, а бывают и вообще пустые ("б\н"). Здесь вариативность тоже большая. Рассмотрим на примере литеры А варианты написания: "литераА", "литА", "лит. а" или просто прикрепляют букву к номеру дома, сама буква может быть как на кириллице, так и на латинице и т.д. Здесь нормализация - это приведение значений к вышеуказанной 3-х элементной модели, после чего перевод в строку.

Для помещений уровня 11 ситуация попроще: номер один, но встречаются сложные буквенно-цифровые комбинации. Здесь также номер нормализуется и записывается строкой.

Если есть ГАР ФИАС, то информация из него позволяет в ряде случаев избавиться от неоднозначности. Например, понять, что "дом 10А" - это "дом 10 литера А", а "дом 10/1" - это "дом 10 квартира 1". Или "дом 10 к.1" - квартира или корпус.

Использование ГАР ФИАС

В настоящее время в ГАР ФИАС около 1.8 млн. объектов уровней 1-8, 49 млн. домов и участков и 54 млн. помещений. Их можно использовать для нормализации! Если удаётся адресный элемент привязать к объекту ГАР, то его уникальный GUID можно считать дополнительной строкой для сравнения (кстати, возможны несколько GUID из-за дублирования в самой базе). Это также избавит от от некоторых неоднозначностей, упомянутых выше.

При привязке следует использовать такие же алгоритмы нормализации. То есть текстовые описания ГАР объектов прогонять через тот же алгоритм нормализации, формируя множественные варианты написания при построении поискового индекса.

Такой способ позволит отождествить, скажем, переименованную улицу и улицу со старым названием (кстати, в свежей версии ГАР информация о переименовании отсутствует, к сожалению, хотя история объектов есть - а раньше старые названия были, по крайней мере, в ФИАС времён DBF).

Адрессарий - система идентификации адресов

Всё вышеизложенное позволяет выдвинуть идею адресного индекса (Адрессария). В него добавляются адреса, он разбивает адрес на элементы, пытается привязать их к уже существующим, если не получается, то добавляет их, достраивая тем самым внутри себя иерархическую систему адресов. Адресные элементы в Адрессарии имеют уникальные идентификаторы, так что его можно рассматривать как систему идентификации адресов.

Пропущенный через Адрессарий, адрес получает числовые идентификаторы его элементов. Два адреса совпадают, если равны идентификаторы его самых низкоуровневых элементов.

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

И это не в будущем, а есть уже сейчас!

SDK Pullenti Address

Всё вышеизложенное реализовано в SDK Pullenti Address:

  • выделяет адреса как из произвольных текстов, так и из "текстового поля" с вычислением коэффициентов качества такого выделения;

  • разбивает на адресные элементы и осуществляет нормализацию;

  • привязывает к объектам ГАР ФИАС, причём работа с базой ведётся не через API к внешнему ресурсу, а со специально построенным индексом в локальной файловой системе, то есть может автономно работать в "закрытом контуре";

  • осуществляет поиск объектов ГАР по разным признакам;

  • поддерживает работу с адресными индексами (Адрессариями);

  • реализовано в виде SDK для языков C#, Java, Python и Javascript и распространяется в виде исходных кодов, не требующих для своего функционирования никаких сторонних библиотек и ресурсов;

  • есть online-демонстрация на сайте, там же можно скачать свежие версии;

Автору на протяжении ряда лет несколько раз приходилось сталкиваться с задачей обработки адресов. Результаты этих столкновений оформлены в виде SDK и предлагаются Вашему вниманию.

Комментарии (15)


  1. TsarS
    19.06.2022 19:37

    Интересно. У вас там пример - "Вот такой адрес: в Москве на улице 16-й Парковой в доме номер 2, кв.3 произошло интересное событие". Если ввести эту же фразу в dadata - он разбирает тоже правильно. А какая у вас стоимость за "стандартизацию"? У dadata 10 копеек за запись. Первые 100 записей — бесплатно.


    1. KonstantinSmith Автор
      19.06.2022 19:42

      В отличие от DaData, здесь стоимость за всё SDK без ограничений, так как обработка производится локально, без обращения к внешним ресурсам. Цена - договорная.


    1. e-serg
      20.06.2022 13:55

      Проверил, dadata.ru ошибается на фразе "664050, Жукова 11 365"

      Геокоординаты: 52.3487025, 104.2296791 maps соответственно не верны.

      В адресе индекс правильный и некорректное сокращение названия проспекта,.

      В результате не тот район города, самоуверенно поставлен другой индекс.


      1. KonstantinSmith Автор
        20.06.2022 13:59

        Человек всегда может придумать пример (в области обработки текстов), который будет не по зубам самому лучшему алгоритму. Здесь всегда останется процент ошибок, для этого и используются разные метрики - полнота, точность и F-мера. Если сравнивать разные системы, то по этим метрикам на некотором количестве адресов (аналогично как проводят конкурсы по NER и т.п.). А так всегда можно найти пример адреса, понимаемый одной системой и не понимаемый другой.


  1. yukon39
    20.06.2022 11:59
    -1

    «поселение Михайловский»

    И эти люди защищают русский язык? Конечно, канцелярит это отдельное подмножество языка, но он ж как-то должен подчинятся общим принципам грамматики. Разве нет?

    Банальное «возбуждено уголовное дело» хоть и отличается от общеязыкового «заведено уголовное дело», но хоть остается в рамках грамматики русского языка. Тут же какой-то грамматический трэш.


    1. KonstantinSmith Автор
      20.06.2022 13:47

      Думаю, здесь дело в том, что раньше был какой-нибудь аул или посёлок Михайловский, а потом стал поселением, но название менять не стали.


  1. kretuk
    20.06.2022 13:48

    чувствительно к регистру:
    «г. москва ул. кржижановского 15-2-1»
    и к разделителям:
    «г. москва ул. Кржижановского 15-2-1
    г. москва ул. Кржижановского 15/2/1»


    1. KonstantinSmith Автор
      20.06.2022 13:54

      Да, с регистром пока неважно - алгоритмы ищут адреса в произвольных текстах, и если учитывать все слова в нижнем регистре, то можно зацепить много мусора. Планирую убрать это требование при обработке текста из одного адреса (в SDK две основные функции - выделение из произвольных текстов и обработка одного адреса из "поля ввода").

      Насчёт разделителей - это вообще больная тема. 15-2-1 - это что? Тут даже человек не разберёт. Например, в Забайкальском крае 10/1 обычно обозначают дом 10 и квартиру 1. В других местах это просто номер через дробь. Часть неопределённости снимается при использовании информации из ГАР ФИАС. Но в общем случае неопределённость в таких случаях неразрешима.


      1. kretuk
        20.06.2022 14:03

        в общем случае вообще ничего неразрешимо))
        именно вот этот dadata эти два случая обрабатывает одинаково:
        «г. москва ул. Кржижановского 15-2-1
        г. москва ул. Кржижановского 15/2/1»
        правильно или не правильно, это другой вопрос, но одинаково. и для человека это вобщем тоже, одно и тоже.


      1. kretuk
        20.06.2022 14:08

        и насчет регистра:
        вот если написано вот так — «г. москва ул. кржижановского» — то «кржижановского» это ну прям точно улица адреса (т.к. слово редкое и в словаре есть), если вот так — «московская область, деревня хуево-кукуево» или «деревня малые ямки» — тут уже вопрос адрес это или нет. вощем получается со словарным подходом надо ещё словарь доразмечать — что там ну прям точно адреса, а что не точно…


        1. KonstantinSmith Автор
          20.06.2022 14:12

          Согласен. Думаю, в следующей версии SDK это будет учтено. Просто в тех довольно больших массивах адресов из разных регионов, которые мне приходилось обрабатывать, случаев названий полностью в нижнем регистре исчезающе мало, и я пока ими пренебрёг.


  1. sshikov
    20.06.2022 20:29

    А вы умеете (и позволяете) достраивать индексы? Ну т.е. вот в моем проекте одним из основных требований (которым, кстати, тогда не удовлетворял движок дадаты) было именно это умение. То есть, я прогнал через движок некоторое число адресов (обычно очень большое, порядка миллионов), получил кучку ответов, которые не были распознаны, сверил их ручками с чем-то (либо с реальностью, либо с другой картой типа Google/Yandex, убрал неопределенность — могу я все это достроить в ваш индекс, взятый из ФИАС, чтобы через неделю адрес уже распознался успешно, даже если завтра ФИАС обновится?


    1. KonstantinSmith Автор
      21.06.2022 14:43

      Индекс ГАР ФИАС достраивать нет смысла - он фиксирован и полностью перестраивается от версии к версии. Там объекты имеют вполне определённую структуру, уникальные GUID и пр. Но он - лишь вспомогательный инструмент при анализе адресов, который даёт возможность убедиться в существовании того или иного объекта, а также убрать неопределённость в ряде случаев.

      Для Вашей задачи больше подойдёт Адрессарий - в него можно неограниченно добавлять новые адреса. Правда, только в рамках существующего алгоритма.

      Поясню на примере. В недавней загрузке было много вот так оформленных адресов: "196620,Санкт-Петербург г,Пушкин г,Анциферовская(Гуммолосары) ул", которые на самом деле "город Санкт-Петербург муниципальный округ город Пушкин территория Гуммолосары улица Анциферовская", то есть в скобках внутри улицы уровня 8 указывался объект уровня 7. После доработки алгоритма эти случаи стали обрабатываться корректно. Но без доработки алгоритма внешними настройками решить эту задачу проблематично.

      Так алгоритм Pullenti совершенствуется от загрузки к загрузке.


      1. sshikov
        21.06.2022 16:10

        Да, видел такие адреса — очень много проблем они вызывали.


  1. lisper
    21.06.2022 12:05

    Любопытно, спасибо. Кстати, вот здесь https://habr.com/ru/company/hflabs/blog/260601/ есть несколько примеров реально существующих, но необычных адресов. Почти все из них разбираются на сайте правильно, кроме одного - "Бывают адреса без улицы. И даже без дома.", например: Звенигород, Супонево, корп 1.