Найти и выбрать квартиру в современном мире — что может быть проще? Берёшь смартфон, скачиваешь приложение и находишь подходящий вариант. Так же просто разместить объявление о продаже или аренде недвижимости. Пользователи смотрят десятки квартир в поисках подходящей — качество ремонта для них очень важно. Вот было бы классно, если бы существовал фильтр, который может правдиво оценить новизну ремонта и отсортировать…

Меня зовут Ирина Говорова, и сейчас я расскажу, как во время моей стажировки в Циан наша команда разработала фильтр «бабушкин ремонт», способный распознавать и классифицировать фотографии помещений.

От собеседования на стажировку до реального проекта

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

Всё началось с того, что в мае 2021 года на одном из ресурсов ODS (Open Data Science) я увидела объявление о наборе стажёров в Циан и отправила заявку. А дальше обычная история: от «Google Форм» и тестирования до собеседования-интервью, где также было тестовое задание.

В общем, это стандартные тесты, по содержанию похожие на тесты для стажёров в других компаниях. Там были вопросы по базовым терминам: что такое случайный лес, как работает backpropagation и т. д. На них сможет ответить любой, кто целенаправленно изучает data science.

На тот момент я училась на первом курсе магистерской программы «Науки о данных» ВШЭ. Именно там на одном из курсов я впервые погрузилась в deep learning, и мне понравилась эта область. Ранее я проходила различные онлайн-курсы по классическому ML, участвовала в хакатоне в треке Data Science. 

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

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

Что мы должны были разработать? Описание задачи

Выхожу на работу — теперь я в команде разработчиков Циан. Наша задача — обучить нейронную сеть, которая будет фильтровать объявления, публикуемые пользователями. Тут легче объяснить на примере.

Допустим, клиент выкладывает объявление с фотографиями квартиры. Наш фильтр в режиме реального времени анализирует прикрепленные изображения, чтобы определить бабушкин ремонт. Самый яркий признак бабушкиного ремонта — старый ковёр на стене спальни (об остальных критериях расскажу позже). Если объявление имеет достоверные теги, оно получает метку «не бабушкин ремонт».

Если обобщать, то бабушкин ремонт выглядит в нашем понимании примерно так:

Именно такие ремонты фильтр должен называть бабушкиными. Благодаря нашему фильтру вам будет проще найти квартиру с новым и хорошим ремонтом или наоборот — не переплачивать за чужой ремонт при покупке жилья.

Давайте покажу, как этот фильтр работает с точки зрения юзера, и постараюсь объяснить, как он устроен изнутри.

Бабушкин ремонт на практике: как работает фильтр в приложении на iOS и Android

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

Для примера поищем в Санкт-Петербурге и окрестностях квартиры стоимостью 3–7 млн рублей. Не забываем выбрать фильтр «бабушкин ремонт».

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

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

Теперь главное, чтобы в категории «хороший ремонт» не было фотографий со старыми коврами и ветхой мебелью. Смотрим:

Отлично! Среди объявлений, которые выдал поисковик, нет фотографий с бабушкиным ремонтом.

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

Архитектура и фреймворк фильтра

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

В самом начале работы над проектом надо было определиться, какую архитектуру мы будем использовать. Решили остановиться на двух семействах архитектур: EfficientNet и ResNet. Они популярны на рынке, довольно «лёгкие» и неплохо себя зарекомендовали.

Мы выбрали свёрточные сети EfficientNet, потому что на наших данных в тесте они показывали лучшие результаты по сравнению с ResNet. По показателям экономии ресурсов и скорости на инференсе нам подходила EfficientNet версии B4. На ней и остановились.

В качестве фреймворка мы использовали PyTorch, потому что он используется в моей команде для других проектов, и я его знала. Также он оказался весьма удобным при конвертировании модели в Open Neural Network eXchange (ONNX)onnxruntime через torch.onnx.export.

Конвертация понадобилась потому, что нам потребовался быстрый инференс на CPU, а обучение мы построили на PyTorch на GPU. Теперь мы могли использовать OpenVINO, который максимизировал производительность сетки на процессорах Intel, установленных в наших машинах. В OpenVINO нельзя напрямую конвертировать PyTorch-модель, зато можно конвертировать из PyTorch в ONNX, а уж из него — в OpenVINO. Кстати, сама конвертация модели в OpenVINO — крайне простой процесс (зато сколько ресурсов позволяет сэкономить!).

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

Асинхронный инференс
from openvino.inference_engine import IECore, StatusCode
ie = IECore()
net = self.ie.read_network(model='model.xml', weights='model.bin’)
net.batch_size = 1
exec_net = self.ie.load_network(network=net, device_name='CPU', num_requests=20)
input_name = next(iter(exec_net.input_info))
output_name = next(iter(exec_net.outputs))
requests_dict = {}
for i in range(len(transformed_images)):
    request_id = self.exec_net.get_idle_request_id()
    exec_net.requests[request_id].async_infer({input_name:input_images[i].unsqueeze(0)})
    requests_dict[i] = request_id
done_requests = set()
while True:
    	for i in range(len(input_images)):
						infer_status = self.exec_net.requests[requests_dict[i]].wait(0)
						if infer_status != StatusCode.OK or i in done_requests:
										continue
						prediction_openvino=exec_net.requests[requests_dict[i]].output_blobs[output_name].buffer
						done_requsts.add(i)
			if len(done_requests) == len(input_images):
						break

Расшифровка. При загрузке модели из файлов с помощью load_network в параметрах указывается num_requests, который показывает, сколько изображений одновременно может процесситься моделью. Затем для каждой картинки из батча определяется свой номер потока, в котором она будет процесситься через get_idle_request_id(). Отдельно в потоках запускается асинхронный инференс картинок с помощью async_infer(). Как только аутпуты потоков готовы, просходит загрузка в итоговый список с выходными данными для всего батча.

С учётом того, что я впервые пользовалась фреймворком OpenVINO, в само́м Циан до нас его никто не юзал, и отсутствовали какие-либо наработки, — на мой взгляд, получилось весьма неплохо.

Выборка, «Яндекс.Толока» и аугментации. Формирование датасета

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

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

1. Откуда брать картинки для датасета

Каждая конкретная задача в некотором смысле уникальна, и наша — не исключение. Казалось бы, найди себе готовый и размеченный датасет на каком-нибудь Kaggle или Dataset Search и пользуйся на здоровье, но нет. Не нашлось ни одного агрегатора, где отыскался бы датасет по типу CelebA с полностью готовыми изображениями бабушкиных квартир и разметкой. Пришлось всё делать ручками.

Первой идеей сбора изображений было парсить картинки по ключевым запросам: «бабушкин ремонт», «хороший ремонт», «евроремонт» — в общем, все возможные виды ремонтов. Увы, адекватных результатов этот метод не дал, потому как действительно релевантная выдача была в радиусе первых трёх десятков картинок, а дальше — сплошной мусор.

Но нет худа без добра. Мы нашли по ключевым запросам в поисковиках и самостоятельно отобрали примеры и уже на их основе отправились искать похожие фотографии в базе Циан. Вот уж действительно, где ещё искать фотографии самых разных интерьеров, если не тут? И наконец на нас посыпались куда более релевантные результаты.
Однако по-прежнему не было гарантии, что даже из небольшой выборки (допустим, около сотни фотографий) все изображения будут подходящими. Фотографии всё равно пришлось отсматривать вручную.

2. Толокисты и разметка данных

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

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

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

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

Например, совершенно не «бабушкина» спальня (p = 0,0629):

А вот фотография комнаты, которая подходит под критерии «бабушкин ремонт» по всем параметрам ​​(p = 0,9401):

На этой кухне ремонт современный (p = 0,1046):

А вот кухня с «бабушкиным ремонтом» (p = 0,9809):

Понятно, что формирование и разметка датасета — дело небыстрое, а времени у нас не было. Так что мы решили воспользоваться «Яндекс.Толокой», пользователи которой за небольшую плату анализируют и размечают данные. Всего на «Толоке» мы разметили 1000 фотографий, взятых из нашей базы.

За выполненные задачи толокисты получают денежное вознаграждение, но они также могут ошибаться. Проблема в том, что само восприятие бабушкиного ремонта — субъективный момент. Невозможно формализовать задачу на 100%: все варианты ремонтов просто нельзя учесть. Но мы просматривали результаты разметки и исправляли самые явные ошибки. В спорных моментах доверялись толокистам.

На основании агрегированных ответов мы собрали датасет, содержащий два класса: «бабушкин ремонт» и «не бабушкин ремонт». Но это ещё не было решением нашей задачи.

3. Должен царствовать баланс…

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

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

Мы отобрали примерно 1000 случайных картинок, которые имеют вероятность p ⩾ 0,3 и потенциально могут относиться к классу «бабушкин ремонт», но среди них была небольшая доля не бабушкиных ремонтов. Затем разметили их на «Толоке». У нас получился датасет (более 2000 картинок), на котором мы стали обучать нейронную сеть — первую версию фильтра. 

Напоследок мы таким же образом набрали ещё около 3,5 тыс. картинок — дополнительный набор изображений, где доля бабушкиных ремонтов была больше, чем не бабушкиных. 

Получилась вполне сбалансированная выборка, примерно 50 на 50. Итоговый обучающий датасет вырос до 5,5 тыс. 4400 изображений пошло в обучающую выборку, а 1100 — в тестовую.

Препроцессинг изображений

Конечно, нейронке нельзя скармливать изображения просто так: они должны пройти этап препроцессинга. При тестировании первой версии фильтра выдавались неплохие результаты, а precision был равен ≈0,78. Но нас такая точность не удовлетворяла. Помните, как важен для обучения хорошо обработанный датасет? Вот и над картинками для фильтра нам пришлось поработать, ведь далеко не всегда проблема именно в модели: бывает, что данные слишком разношёрстные.

Мы использовали классический набор для преобразования изображений: поворот на малый угол, horizontal flip и регулировки яркости и контрастности.

Немного аугментаций — отличный способ искусственно увеличить выборку и сделать сеть более устойчивой к небольшим изменениям фотографий. Всё это помогло добиться precision ≈0,86, а recall — ≈0,82.

Отдельно надо сказать о ресайзе. Модель OpenVINO принимает на вход картинки фиксированного размера. В примерах инференса, которые я использовала, применялся ресайз из OpenCV, который работает с картинками в виде NumPy-массивов. Да и OpenVINO-модель принимает на вход NumPy-массив. Мы заменили ресайз OpenCV на ресайз модуля Pillow (оба дефолтные). В своей основе эти два ресайза (Pillow и OpenCV) используют отличные друг от друга алгоритмы: на выходе сжатые картинки воспринимаются нейронной сетью по-разному, хотя для человеческого глаза разницы нет. Применение к изображениям ресайза, который изначально использовался при обучении, дало нам адекватный инференс и полное совпадение результатов OpenVINO-модели и оригинальной модели на PyTorch. Я ответственно заявляю: мелочи важны!

Что у нас получилось

После длительной сборки датасета (поиска изображений, их разметки и препроцессинга) и обучения сетки на внушительной итоговой выборке мы добились показателей precision ≈0,98 и recall ≈0,5 при пороге классификации 0,95. Неплохо, не так ли?

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

Фильтр получился более чем работоспособным. Об этом говорят не только метрики, но и первые оценки от пользователей приложения на iOS. Это популярный фильтр арендных квартир, и он очень востребован у тех, кто ищет «бабушкины» квартиры для покупки. Уже сейчас каждый желающий, у кого есть смартфон на iOS или Android, может самостоятельно протестировать фильтр «бабушкин ремонт» и оставить свой отзыв о нём (например, в комментариях к посту).

Напутствие для инициативных

Каждый сервис, если есть такая возможность, должен оперативно разрабатывать технологии, которые помогут клиентам сберечь время и деньги. Задачей нашего проекта было улучшить Циан так, чтобы каждый человек мог ещё проще найти квартиру своей мечты. Я очень надеюсь, что наш фильтр зайдёт простым юзерам, а может, сподвигнет и вас на создание новых решений. Призываем пробовать, открывать и тестировать. Нам очень важен фидбэк, так что смелее!

Тех, кого больше интересует техническая реализация, у кого есть вопросы или интересное мнение, — приглашаем в комментарии!

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


  1. tuxi
    21.03.2022 10:35
    +6

    Просили фидбек - получите. Если в комнате есть розетки на каждой стене и не накладные, а "встроенные" и не по 1 штуке, а по 2...3...4 , то это точно не бабушкин ремонт... даже если висят ковры и стоят комоды позапрошлого века.


    1. gvrva Автор
      21.03.2022 12:48
      +1

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


      1. tuxi
        21.03.2022 14:26
        +1

        Так можно запросить указать кол-во стеновых розеток по всей квартире (или комнатам) и уже будет показатель, что там за ремонт.


        1. Amihailov
          22.03.2022 10:01

          Тогда можно и запросить указать бабушкин ремонт или нет :)

          А вообще - не надо мучать людей лишними вопросами. Путь подачи объявления должен быть максимально простым.


          1. tuxi
            22.03.2022 12:31
            +1

            Ну понятие «бабушкин ремонт» трудно формализуемо. Кому то ковры 16-го века ценой под 10кевро покажутся бабушкиным ремонтом.
            А розетки вот они. Их можно посчитать. И проверить, что их именно столько, сколько написано в обьявлении.

            И подача объявления в циан ответственный и небыстрый процесс, количество розеток не сильно его усложнит)))


  1. Zibx
    21.03.2022 10:36
    +4

    Подумайте о заворачивании этой нейронки as a service. Множество стартапов из других стран будут готовы платить за неё хорошие деньги


    1. kapany3
      21.03.2022 12:22
      +5

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


      1. Zibx
        22.03.2022 15:39

        Наши айтишники поехали в другие страны и в Грузии, Азербайджан, Армении, Казахстане дикое количество бабушкиного ремонта и ужасные сервисы. Друг пробовал арендовать на AirBnB с включенным instant booking и на Букинге со схожей галочкой. В итоге пришлось 5 раз общаться с суппортом чтоб деньги вернули, потому что по звонку рассказывали что уже 3 года квартира не сдаётся и доступа к аккаунту нет.


  1. tark-tech
    21.03.2022 12:26
    +16

    >Например, совершенно не «бабушкина» спальня (p = 0,0629):

    Лучше 'бабушкина' спальня чем 'цыганский шик'...


  1. DaneSoul
    21.03.2022 13:16
    +3

    Очень важно для чего ищется квартира.
    Если это аренда — тут можно и на ковры и шкафы смотреть при оценке, а если покупка, то важны другие критерии:

    • окна ПВХ или старье?
    • Что за покрытие на полу?
    • Есть ли плитка и в каком состоянии в сан.узле?
    • Какие розетки и в каком количестве?
    • Какое состояние труб в сан-узлах?
    • Какое состояние дверей?
    Вот перечисленное выше может потребовать больших денежных и временных затрат после покупки.
    А выкинуть ковры и старые шкафы и поставить ту мебель что нравятся — на это готово большинство покупателей и так изначально.


    1. Vsevo10d
      22.03.2022 01:22

      Не скажите, состояние труб санузла, розеток и окон - первое, что я смотрел при аренде. Когда тебе надо ходить на работу, а тебе однажды говорят, что канализация течет к соседям снизу, а сантехник жилищника будет с 9:00 до 19:00, это как-то наматываешь на ус.

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


  1. Earthsea
    21.03.2022 14:36
    +3

    Смотрел квартиры в Сарагосе, Испания. Там бабушкиных ремонтов столько, что Москва просто отдыхает. Видел все: и шкафы-стенки, и арки, и чудовищную плитку в санузлах, и ковры (не на стенах правда), и разрозненные старые шкафчики на кухнях.

    Раньше думал что все это только для СНГ характерно, шаблон сломался.


  1. RGrimov
    21.03.2022 15:41

    А не смотрели через GradCAM или аналоги на то по каким именно деталям квартира классифицируется как "бабушкина"?


    1. gvrva Автор
      21.03.2022 18:13
      +1

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


  1. jazator
    21.03.2022 18:40
    -1

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


  1. eboyar
    21.03.2022 21:18
    +1

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

    Ищу б/у блок питания на всем известном сайте, автоматом предлагается категория "Блоки питания". Вроде бы все хорошо, но потом оказывается, что существует ещё и "Блоки питания и батареи", и совсем размытое "Электрика", и подходящий тебе лот продавец мог опубликовать в любой из них.

    В итоге несмотря на все удобство фильтров, приходится от них отказываться, потому что "А вдруг он ошибочно отфильтрует и нужное?"


    1. gvrva Автор
      22.03.2022 14:03

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


  1. Vsevo10d
    22.03.2022 01:14
    +7

    Хм, я вот нищеброд с медианной зарплатой, поэтому выберу скорее бабушкин ремонт, поскольку с 99 процентной вероятностью белый советский кухонный столик-раскладушку я выкину и заменю на дешевый икеевский, а бармен будет только рад хозяева даже слова не скажут. И так же с табуретками, мелочевкой в санузле и т.д. А с современным ремонтом - плавали, знаем – "вот тут ламинат свежий постелили, пари над ним как Мастер Йода, по варочной панели кастрюлей не елозь, в унитаз сильно не перди, мы все сфоткали".

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


  1. SeaAngel
    22.03.2022 12:02

    Несколько месяцев назад переезжал в Москву

    И «бабушкин» ремонт от «не-бабушкиного» фильтровал просто ценой.

    А вот чего очень не хватало, это фильтра по ветке метро, вот хотелось ехать на работу просто по прямой без пересадок, но вот постоянно предлагают указать именно нужные станции. Каждый раз вбивать 10+ станций не так весело


    1. gvrva Автор
      22.03.2022 14:09

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


      1. SeaAngel
        22.03.2022 14:22

        О, действительно, видимо я ожидал этот фильтр в другом разделе. Спасибо за совет, думаю скоро снова пригодится


  1. BM_MacGregor
    22.03.2022 15:39

    Вопрос чисто организационный.

    Был смысл пользоваться Толокой? У Вас там итоговый датасет в 5.5 тысяч картинок. По личному опыту, это можно и самому разметить.

    Вы же их еще и перепроверяли.


  1. Lexer11l
    22.03.2022 16:25
    -1

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