Расскажу, как классификация текста помогла мне в поиске квартиры, а также почему я отказался от регулярных выражений и нейронных сетей и стал использовать лексический анализатор.

Примерно год назад мне необходимо было найти квартиру для съема. Больше всего объявлений от частных лиц публикуется в социальных сетях, где объявление пишется в свободной форме и для поиска нет никаких фильтров. Вручную просматривать публикации в разных сообществах долго и неэффективно.

На тот момент уже существовало несколько сервисов, которые собирали объявления из социальных сетей и опубликовывали их на сайте. Таким образом, можно было увидеть все объявления в одном месте. К сожалению, там также отсутствовали фильтры по типу объявлений, цене. Поэтому спустя какое-то время я захотел создать свой сервис с необходимым мне функционалом.

Классификация текстов


Первая попытка (RegExp)


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

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

Регулярные выражения, которые использовались в тестах
- '/(комнат|\d.{0,10}комнат[^н])/u'
- '/(квартир\D{4})/u'
- '/(((^|\D)1\D{0,30}(к\.|кк|кв)|одноком|однуш)|(квартир\D{0,3}1(\D).{0,10}комнатн))/u'
- '/(((^|\D)2\D{0,30}(к\.|кк|кв)|двух.{0,5}к|двуш|двух.{5,10}(к\.|кк|кв))|(квартир\D{0,3}2(\D).{0,10}комнатн))/u'
- '/(((^|\D)3\D{0,30}(к\.|кк|кв)|тр(е|ё)х.{0,5}к|тр(е|ё)ш|тр(е|ё)х.{5,10}(к\.|кк|кв))|(квартир\D{0,3}3(\D).{0,10}комнатн))/u'
- '/(((^|\D)4\D{0,30}(к\.|кк|кв)|четыр\Sх)|(квартир\D{0,3}4(\D).{0,10}комнатн))/u'
- '/(студи)/u'
- '/(ищ.{1,5}сосед)/u'
- '/(сда|засел|подсел|свобо(ж|д))/u'
- '/(\?)$/u'


Такой метод для тестового набора дал 72.61% правильных ответов.

Вторая попытка (Нейронные сети)


В последнее время стало очень модно использовать машинное обучение для всего что угодно. После обучения сети сложно или даже невозможно сказать, почему она решила именно так, но это не мешает успешно применять нейронные сети в классификации текстов. Для тестов использовался многослойный перцептрон с методом обучения обратного распространения ошибки.

В качестве готовых библиотек нейронных сетей использовались:

FANN написана на C
Brain написана на JavsScript

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

Для этого из всех текстов тестовой выборки были выявлены n-граммы из более чем 2х символов и повторяющиеся в более чем 15% текстов. Таких оказалось чуть больше 200.

Пример n-грамм
- /ные/u
- /уютн/u
- /доб/u
- /кон/u
- /пол/u
- /але/u
- /двух/u
- /так/u
- /даю/u

Для классификации одного объявления в тексте искались n-граммы, выяснялось их расположение, и затем эти данные подавались на вход нейронной сети так, чтобы значения были в переделах от 0 до 1.
Такой метод для тестового набора дал 77.13% правильных ответов (при том, что тесты выполнялись на той же выборке, на которой производилось обучение).

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

Третья попытка (Синтаксический анализатор)


В то же время я стал больше читать статей про обработку естественного языка и наткнулся на прекрасный парсер Tomita от Yandex. Основное его преимущество перед другими подобными программами состоит в том, что он работает с русским языком и имеет довольно внятную документацию. В конфигурации можно использовать регулярные выражения, что очень кстати, поскольку часть из них у меня уже была написана.

По сути своей, это намного более продвинутая версия варианта с регулярными выражениями, но намного более мощная и удобная. Здесь также не обошлось без предварительной обработки текста. Текст, который пишут пользователи в социальных сетях, часто не отвечаeт грамматическим и синтаксическим нормам языка, поэтому у парсера возникают трудности при его обработке: разбиении текста на предложения, разбиении предложений на лексемы, приведении слов к нормальной форме.

Пример конфигурации
#encoding "utf8"
#GRAMMAR_ROOT ROOT

Rent -> Word<kwset=[rent, populate]>;
Flat -> Word<kwset=[flat]> interp (+FactRent.Type="квартира");
AnyWordFlat -> AnyWord<kwset=~[rent, populate, studio, flat, room, neighbor, search, number, numeric]>;

ROOT -> Rent AnyWordFlat* Flat { weight=1 };


Все конфигурации можно посмотреть тут. Такой метод для тестового набора дал 93.40% правильных ответов. Помимо классификации текста из него также выделяются факты, такие как: стоимость аренды, площадь квартиры, станция метро, телефон.

Попробовать парсер онлайн
Запррос:
curl -X POST -d 'сдаю двушку 50.4 кв.м за 30 тыс в месяц. телефон + 7 999 999 9999' 'http://api.socrent.ru/parse'

Ответ:
{"type":2,"phone":["9999999999"],"area":50.4,"price":30000}

Типы объявлений:
0 — комната
1 — 1 комнатная квартира
2 — 2 комнатная квартира
3 — 3 комнатная квартира
4 — 4+ комнатная квартира


В итоге, при небольшом тестовом наборе и необходимости высокой точности получилось выгодней писать алгоритмы вручную.



Разработка сервиса


Параллельно с решением задачи классификации текста было написано несколько сервисов для сбора объявлений и представления их в удобном для пользователя виде.

github.com/mrsuh/rent-view
Сервис, который отвечает за отображение.
Написан на NodeJS. Использовались шаблонизатор doT.js и БД Mongo.

github.com/mrsuh/rent-collector
Сервис, который отвечает за сбор объявлений. Написан на PHP. Используются фреймворк Symfony3 и БД Mongo.
Писался с расчетом собирать данные из разных источников, но, как оказалось, почти все объявления размещаются в социальной сети Вконтакте. У данной соцсети есть отличный API, поэтому не составило труда собирать объявления из стен и обсуждений в публичных группах.

github.com/mrsuh/rent-parser
Сервис, который отвечает за классификацию объявлений. Написан на Golang. Использует парсер Tomita. В сущности, представляет из себя обертку над парсером, но также осуществляет предварительную обработку текста и последующую обработку результатов парсинга.

Для всех сервисов настроен CI с помощью Travis-CI и Ansible (как настроен автоматический деплой, писал в этой статье).

Статистика


Сервис работает уже примерно два месяца для города Санкт-Петербург и за это время успел собрать чуть больше 8000 объявлений. Вот немного интересной статистики по объявлениям за весь период.

В день в среднем добавляют 131.2 объявлений (точнее текстов, которые были классифицированы как объявления).

Самый активный час 12 дня.

Самая популярная станция метро Девяткино


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

Если кто-то захочет сам решить подобную задачу, то вот здесь лежит тестовый набор из 8000 текстов и их типов.
Поделиться с друзьями
-->

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


  1. DanikoBas
    14.05.2017 16:09

    Так сам сервис планируется быть доступным для людей?


    1. mrsuh
      14.05.2017 16:24

      Он уже доступен. Просто я ссылку не оставлял в самой статье(Зато они указаны в репо на github).



  1. guyfawkes
    15.05.2017 01:55

    А подробнее про классификацию на основе нейронной сети можете рассказать или посоветовать материалы?


    1. mrsuh
      15.05.2017 07:50

      Мне казалось, что по статье можно повторить опыт. Скажите, что именно вам непонятно, и я постараюсь рассказать об этом подробней.

      На хабре уже были статьи про классификацию текстов
      https://habrahabr.ru/company/meanotek/blog/256593/
      https://habrahabr.ru/post/130278/

      Обычно используется преобразование слов в векторы с помощью word2vec и результат не превышает 80%. В моем случае так было сделать нельзя из за свободной формы написания объявлений(часто с ошибками и различными сокращениями)


  1. Rast1234
    15.05.2017 02:55

    Несколько лет назад существовал сервис Sobnik, который решал смежную задачу: он парсил сайты с объявлениями о квартирах и отделял фейковые (риэлторские) от реальных. Автор cerber был доступен в VK и отвечал на вопросы, чинил баги. Жаль, что эта штука больше не работает. Посмотрите, может быть найдете что-то полезное.
    https://habrahabr.ru/post/237869/
    https://habrahabr.ru/post/240941/


    1. mrsuh
      15.05.2017 07:56

      Спасибо. Читал обе эти статьи. Пока что борюсь с агентами вручную с помощью черных списков.


  1. alexeisirotkin
    15.05.2017 07:51

    До первой жалобы в ркн этот сайт будет работать. Лучше не дожидаться. Имена-фамилии и телефоны выложены.
    В этом проблема большинства агрегаторов: все хотят пиздить контент, но никто не хочет заниматься актуализацией.


    1. mrsuh
      15.05.2017 07:52

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


      1. alexeisirotkin
        15.05.2017 08:04

        Весь контент взят из соцсети. Люди давали объявление именно там. Никто на вашем сайте не регистрировался и ничего не размещал. Через месяц информация может быть уже неактуальной, а у вас она будет висеть годами. Пользователей (многих) это бесит.
        Вот лично я, если бы мое объявление взяли, даже не стал бы тратить время на поиск контактов на вашем сайте (которых, кстати, нет), я просто сразу накатал бы жалобу.
        У вас странные представления об «открытом доступе».
        В общем, как я уже писал, вы просто хотите брать бесплатный контент без всяких усилий, при этом не нести никакой ответственности ни за достоверность, ни за что-либо вообще.


        1. DanikoBas
          15.05.2017 08:07

          Если что, объявления удаляют, и можно это отслеживать и так же удалять.
          Единственное, что правда надо добавить — дату размещения объявления чтобы без переходов и лишних кликов знать стоит ли писать. Ну и ссылаться не на личку, а на профиль чтобы обманщиков сразу вычислять


          1. alexeisirotkin
            15.05.2017 08:13

            1. Проблема в том, что и посетителей это дезориентирует. Часто бывает, связываешься с кем-то по телефону, а в ответ тирада о том, что объявление давали уже год назад или даже раньше, ну а потом оно пошло по разным нано-сайтам и будет еще ходить очень долго.
            2. Проблема личных данных существует и это не миф. У меня есть сайт с регистрацией, пользователи сами размещают анкеты, тем не менее уже 2 раза РКН меня беспокоил по поводу жалоб. В первом случае пользователь был немного неадекватный и то ли забыл, что регистрировался, то ли даже не знаю — в общем, накатал жалобу, что его данные у меня на сайте размещены без его согласия.
            Во втором случае просто кто-то разместил не свою информацию.
            Оба раза хостер блокировал работу сайта, пока я не вышлю ему сканы документов, а после этого со мной связывался Роскомнадзор.


        1. DanikoBas
          15.05.2017 08:08

          А касательно законности я бы посоведовал разузнать какие требования и выполнить их. Сервис имеет будущее, нужно лишь продолжать работать


        1. mrsuh
          15.05.2017 08:26

          По поводу актуализации данных: на сайте представлены данные только за последние две недели(каждый день объявления старше двух недель переносятся в холодную БД). Остальные объявления доступны только по прямой ссылке.

          https://vk.com/licence

          Размещая информацию в Социальной сети, в том числе учетные и иные данные, Лицензиат соглашается, что такая информация может быть доступна другим пользователям сети Интернет с учетом существующего функционала Социальной сети (который может изменяться время от времени Лицензиаром), а также что Лицензиар может ограничивать использование третьими лицами информации из Социальной сети, в том числе в коммерческих целях


          По поводу ркн спасибо, нужно еще почитать и посоветоваться с знающими людьми.


          1. mrsuh
            15.05.2017 08:33

            Нашел небольшую статью у ркн

            https://77.rkn.gov.ru/p3852/p13239/

            3. Вопрос: Является ли обработкой персональных данных размещение фамилии, имени и отчества без иной дополнительной информации?

            Ответ: Размещение на страницах сайтов в сети «Интернет» фамилии, имени и отчества без дополнительной информации, позволяющей идентифицировать физическое лицо как субъекта персональных данных, не может свидетельствовать об обработке персональных данных конкретного физического лица.

            Обращаем Ваше внимание на то, что при размещении персональных данных в публичных сообществах социальных сетей следует разграничить вопросы защиты персональных данных и защиты чести, достоинства и деловой репутации.

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

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


            1. alexeisirotkin
              15.05.2017 08:49

              Но вы-то взяли не из профиля, а из объявления. Которое при необходимости пользователь может удалить вконтакте. Но почему он еще вынужден потом удалять очень долго это скопированное объявление на 100500 сайтах — вопрос интересный.


              1. haldagan
                15.05.2017 09:01

                Вы, судя по всему не уловили сути: пользователь разместил информацию в публичном доступе — все, ни о какой приватности речи больше не идет.

                Если вы объявления на заборах расклеите, а через день их снимите — вам так же будут с недельку-другую еще названивать.


                1. alexeisirotkin
                  15.05.2017 09:14

                  Вопрос в том, что я размещал информацию, но не на сайте автора.
                  Впрочем, мне все равно. Жду автора на форуме серча в этом году со слезливой историей о том как его заблокировали и что теперь делать :)


                  1. haldagan
                    15.05.2017 09:27

                    не на сайте автора

                    В публичном доступе(общественном месте) — это где угодно, куда человек может беспрепятственно зайти(пройти).

                    Если вы размещаете свою информацию в таком месте — вы автоматически разрешаете неопределенному кругу лиц ею пользоваться. В том числе передавать устно и письменно третьим лицам.

                    Если вы не хотите, чтобы ваше объявление утащили на другой сайт — придется пользоваться закрытыми площадками «только по пропускам», у которых в ToS прописано, что они сами не передают никакую опубликованную информацию третьим лицам И запрещают пользователям ее передавать.

                    как его заблокировали и что теперь делать

                    Был бы человек… Поступят жалобы — могут и заблокировать.


        1. tonissimo
          16.05.2017 10:38

          Вот бы можно было в РКН писать, только если налоги заплачены, лицемерия было бы меньше. А то, я сомневаюсь, что лицо дающее объяву в ВК о сдаче квартиры в аренду делает это официально и платит потом НДФЛ.


  1. LionisIAm
    17.05.2017 08:01

    Спасибо! Очень полезно.
    Где нужно менять город в исходниках, чтобы обьявления парсились с нужного города? Спасибо.


    1. mrsuh
      17.05.2017 08:14

      Все это меняется в параметрах:

      необходимо запустить скрипт sh bin/deploy (чтобы создались файлы параметров из dist файлов)

      указать группы и паблики нужного города в конфигах парсера
      https://github.com/mrsuh/rent-collector/blob/master/app/config/parser.yml.dist

      добавить город и станции метро в фикстуры
      https://github.com/mrsuh/rent-collector/blob/master/app/fixtures/city.yml
      https://github.com/mrsuh/rent-collector/blob/master/app/fixtures/subway.yml

      Запустить sh bin/install для загрузки фикстур в БД

      Если что то будет непонятно — пишите в личку