Как я написал программу для преданалитики клиентов
Привет! Меня зовут Александр Кулагин. Я не занимался разработкой профессионально, но заинтересовался созданием нейросетей. После изучения основ Python, NumPy и TensorFlow я захотел попрактиковаться на реальных задачах. Так я решил создать проект, который оценивает, какие компании потенциально заинтересованы в сотрудничестве с конкретным бизнесом.
В этой статье я расскажу о процессе создания проекта: от поиска заказчика и сбора данных до финального этапа — создания веб-интерфейса. Проект претерпевал много изменений, потому что реальность вносила коррективы: иногда у меня не хватало знаний, иногда изначальное решение просто нельзя было применить.
→ Сайт проекта
→ Код проекта на GitHub
При создании нейросети передо мной стоял выбор: написать её по шаблону или разработать полезный проект для реального заказчика. Я пошёл по второму пути. Оговорюсь, что над проектом я работал бесплатно — всё же моей главной целью было закрепление знаний.
У меня не было готовой идеи, при этом я хотел найти реальную проблему бизнеса, решить её и проверить результат в полевых условиях. Среди моего близкого круга предпринимателей не было, поэтому начал искать через знакомых, — так вышел на руководителя компании, которая занималась организацией отраслевых выставок.
Компания хотела оптимизировать поиск клиентов — сотрудникам приходилось тратить много времени на холодные звонки. Заказчику нужен был алгоритм, который предсказывает вероятность участия компании в выставке на основе исторических данных. По задумке он помогает выбирать для обзвона только те компании, которые потенциально могут согласиться на сотрудничество.
Сбор и отсев данных
За три года работы у заказчика накопилась первичная база из 600 компаний, которые участвовали в выставках либо отказывались от участия. Однако использовать её целиком для обучения нейросети было невозможно: данные были разрозненны, не однообразны. Зачастую состояли лишь из названия организации, ИНН и контактов.
Я искал решение и начал с идеи парсинга сайтов клиентов.Идея была в том, чтобы собрать информацию, проанализировать деятельность компании и выделить ключевые критерии для алгоритма. Этот путь оказался слишком сложным по нескольким причинам:
структура сайтов и их наполнение сильно отличались друг от друга, что затрудняло парсинг и определение ключевых показателей компаний;
не у всех компаний из базы были сайты;
слишком большой объем информации.
Тогда я решил собирать данные из агрегаторов: в частности, с сайта RusProfile, где содержится основная информация о компаниях. На тот момент единого решения для парсинга у меня не было, как и времени на написание самого парсера. Можно было бы отдать задачу на аутсорс, но проект был некоммерческий, поэтому я не хотел тратить на это ресурсы.
Тогда я решил использовать готовое решение. Поискал в интернете — Google выдал ссылку на готовый парсер, стоимость и функционал которого меня устроили. Сравнимых альтернатив я не увидел, поэтому воспользовался им. В итоге в моём распоряжении оказалось по 33 показателя для каждой компании: ОКВЭД, ОКАТО, адрес, дата основания и другие.
Часть результатов парсинга
Я решил, что мне не нужны все полученные показатели — лучше выделить только ключевые. Для этого я использовал корреляционную матрицу: она помогла увидеть взаимосвязь всех параметров между собой.
Чем ближе к нулю значение на пересечении параметров, тем меньше их влияние друг на друга
Для обучения нейросети лучше брать как можно более независимые друг от друга параметры — это позволит лучше, более разносторонне охарактеризовать компанию. Так я сократил базу до шести пунктов, оказывающих наиболее значимое влияние:
Год создания организации
Основной ОКВЭД
Тип налогообложения
ОКФС
ОКОПФ
СМСП
На их основе я решил составлять датасет для обучения и приступил к выбору архитектуры.
Выбор архитектуры
Я использовал библиотеку машинного обучения TensorFlow: во-первых, её функционал вполне подходил для задачи, а во-вторых, я её уже хорошо знал. Эта библиотека позволяет довольно просто и гибко подбирать параметры и архитектуру нейронной сети.
Я рассматривал следующие архитектуры:
свёрточная сеть Conv1d;
полносвязная сеть с параллельными ветвями под различные функции активации;
полносвязная прямая сеть.
Свёрточная сеть Conv1d. Показала заведомо худшие результаты. На неё особых надежд не возлагал, попробовал больше для полноты картины.
Полносвязная сеть с параллельными ветвями под различные функции активации. В такой сети данные поступают на общий вход и параллельно обрабатываются отличающимися по параметрам нейронными слоями. После этого данные объединяются и обрабатываются как единое целое. Для моей задачи такой подход оказался не лучшим — слишком сложная архитектура.
Код выглядел следующим образом:
input_1 = Input(shape=(xTrain.shape[1],), name='1')
x_1 = BatchNormalization()(input_1)
x_2 = Dense(units=512, activation='relu', use_bias=True)(x_1)
x_3 = Dense(units=256, activation='softmax', use_bias=True)(x_1)
x_2_1 = BatchNormalization()(x_2)
x_3_1 = BatchNormalization()(x_3)
x_2_2 = Dropout(0.1)(x_2_1)
x_3_2 = Dropout(0.2)(x_3_1)
x_4 = Concatenate()([x_2_2, x_3_2])
x_4 = BatchNormalization()(x_4)
x_4 = Dense(units=512, activation='relu', use_bias=True)(x_4)
output_2 = Dense(units=2, activation='softmax', use_bias=True, name='2')(x_4)
model2 = Model([input_1], [output_2])
Параметры архитектуры
Схема полносвязной сети с параллельными ветвями
Я измерил изменение точности предсказаний нейросети с течением эпох обучения и пришёл к выводу, что точность при длительном обучении может снижаться, происходит переобучение. Я стремился к точности не менее 80%, поэтому эта архитектура не подошла.
Синий график — точность предсказаний на обучающей выборке,
оранжевый — точность предсказаний на проверочной
Полносвязная прямая сеть. Я посчитал, что эта архитектура наиболее перспективная: при меньшем количестве параметров точность предсказаний уже лежала в пределах 80—85% даже при первом запуске чернового варианта. У меня не сохранилась история всех попыток, поэтому поделюсь только первой архитектурой «на глаз»:
model = Sequential()
model.add(Dense(256, activation='relu', input_shape=(xTrain.shape[1],)))
model.add(BatchNormalization())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(128, activation='relu'))
model.add(Dense(2, activation='softmax'))
Параметры архитектуры
Схема полносвязной прямой сети
Показатели средней абсолютной точности
Подбор параметров архитектуры
После того как я определился с архитектурой, нужно было понять, какое количество слоёв с каким количеством нейронов и какими активационными функциями выполнит задачу лучше всего. Чтоб не заниматься перебором вручную, я применил генетический алгоритм.
Алгоритм позволяет тестировать работу нейросети в разных компоновках. Он отбирает лучшие сочетания блоков, после чего скрещивает их случайным образом, создавая новую популяцию. Из этой популяции он снова отбирает лучшие сочетания — и так далее.
Вопросы, над которыми работал генетический алгоритм
Входной слой
Необходимость нормализации;
Размер первого свёрточного слоя;
Ядро первого свёрточного слоя;
Функция активации первого слоя;
Необходимость MaxPooling0;
Размер MaxPooling0.
Первый скрытый слой
Необходимость второго свёрточного слоя;
Размер второго свёрточного слоя;
Ядро второго свёрточного слоя;
Необходимость MaxPooling1;
Размер MaxPooling1;
Функция активации.
Второй скрытый слой
Необходимость третьего сверточного слоя;
Размер третьего сверточного слоя;
Ядро третьего сверточного слоя;
Необходимость MaxPooling2;
Размер MaxPooling2;
Функция активации;
Функция активации предпоследнего слоя;
Третий (предпоследний) скрытый слой
Лучшим сочетанием оказались два слоя по 256 нейронов в каждом, один слой с двумя нейронами и один слой вывода:
model = Sequential()
model.add(Dense(256, activation='relu',input_shape=(xTrain.shape[1],)))
model.add(Dense(256, activation='relu'))
model.add(Dense(2, activation='softmax'))
Параметры архитектуры
Схема готовой нейросети
При использовании полносвязной прямой сети я избавился от проблемы, которая наблюдалась у сети с параллельными ветвями. Ухудшение предсказаний со временем отсутствует, точность предсказаний на проверочной выборке — около 88%. Как видно на графике, показатели улучшились:
Синий график — точность предсказаний на обучающей выборке,
оранжевый — точность предсказаний на проверочной
Я проверял точность работы нейросети по результатам уже прошедшей выставки. У меня были ИНН двадцати компаний: я знал, что половина из них участвовала в выставке, а половина отказалась. Данных по ним в исходном датасете не было.
В шестнадцати случаях прогноз участия оказался верным, в четырёх — ошибочным. Точность при обучении достигала 88%, в реальных условиях подтвердилась точность в 80%.
Создание интерфейса
Когда я разрабатывал проект нейросети, я ещё не умел писать интерфейсы. Проект долго лежал «в ящике» — просто код без интерфейса. Его можно было показать в Colab«е или другом редакторе, но всё же оставалось ощущение оставленной на полпути работы.
Работать над интерфейсом я начал почти год спустя, на январских праздниках. Перед Новым годом я изучил Django с примером простого фронта на курсе «Python-разработчик плюс». Возможно, библиотека и не самая оптимальная для одностраничного сайта, но Flask и FastAPI были ещё впереди.
В качестве шаблона я использовал учебный проект, который сдал накануне. Его пришлось немного упростить и адаптировать под задачи сервиса. По сути, повторил учебный материал последнего спринта и сделал по нему самостоятельную работу. Запустил всё на бесплатном хостинге и с чувством выполненного долга продолжил обучение.
Итоговый интерфейс
Финальный результат
Нейросеть продемонстрировала сравнительно высокую точность предсказаний, но на практике пользоваться ей оказалось неудобно. Нужно было вручную вводить показатели для каждой компании, выпадающие списки задачу не сильно облегчали.
Хотя объём ручной работы сократился, заказчику требовалось более автоматизированное решение: например, чтобы сотрудник загружал список ИНН и этих данных было достаточно для определения потенциальных партнёров.
Разработкой проекта я начал заниматься в декабре 2021 года, а результат показал в феврале 2022-го. Тогда экономическая ситуация сильно изменилась, и аналитика за последние три года, с которой я работал, потеряла актуальность.
Тем не менее я получил опыт, который могу продемонстрировать при поиске работы и в будущем использовать на реальных коммерческих задачах. Результатом доволен, даже несмотря на фактическую невостребованность проекта. Работа над ним позволила увидеть увеличившуюся область моего незнания, что послужило отличным стимулом к продолжению обучения.