{"id":14277,"url":"\/distributions\/14277\/click?bit=1&hash=17ce698c744183890278e5e72fb5473eaa8dd0a28fac1d357bd91d8537b18c22","title":"\u041e\u0446\u0438\u0444\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0442\u0440\u044b \u0431\u0435\u043d\u0437\u0438\u043d\u0430 \u0438\u043b\u0438 \u0437\u043e\u043b\u043e\u0442\u044b\u0435 \u0443\u043a\u0440\u0430\u0448\u0435\u043d\u0438\u044f","buttonText":"\u041a\u0430\u043a?","imageUuid":"771ad34a-9f50-5b0b-bc84-204d36a20025"}

Как заменить регулярные выражения нейронной сетью?

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

Задача: найти в тексте описание стоимости недвижимости, то есть численное обозначение и стоимость, записанную прописью. Например, 2 050 000 (два миллиона пятьдесят тысяч) руб., 00 коп. Задача усложняется тем, что «рубли» и «копейки» могут быть в любом месте (перед скобками или после) и могут быть сокращены.

Чтобы решить данную задачу, будем использовать NLP (Natural Language Processing), морфологический анализатор и нейронную сеть. Подключаем соответствующие библиотеки:

import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers import pymorphy2 from nltk.corpus import stopwords from nltk.tokenize import word_tokenize

Прежде всего необходимо выполнить обработку текста.

1. Токенизируем текст с помощью nltk.

f = open('input/'+file, 'r', encoding='ansi') strings = f.read().replace('\n', ' ') words = word_tokenize(strings, language='russian')

2. Уберем стоп-слова из текста и знаки препинания, которые нам не нужны (например, : или “).

Токенизируем текст с помощью nltk.

3. После этого пройдемся по тексту и выберем фрагменты, в которых встречается слово «рубли» в полном варианте или в сокращенном. В итоге получаем фрагменты предложений, которые содержат одинаковое количество слов/знаков/чисел. Именно в этих фрагментах мы и будем искать стоимость.

Следующим шагом нужно представить «слова» в виде чисел, так как нейронная сеть работает только с числами. Для этого пройдемся по каждому полученному фрагменту и определим части речи для каждого «слова». В этом нам поможет морфологический анализатор pymorphy2.

morph = pymorphy2.MorphAnalyzer() def set_morphema(x): morphema = str(morph.parse(x)[0].tag).split(',')[0] if morphema.find(' ') != -1: morphema = morphema.split(' ')[0] return morphema

При анализе выделим 6 групп значений:

  1. PNCT – знаки пунктуации: ( ) . ,
  2. NUMB – числа
  3. NUMR – числительные
  4. NOUN – существительные: «тысяча», «миллион», «миллиард»
  5. NOUN – существительные: «рубль», «копейка» и их сокращенные формы
  6. Все остальные части речи, которые не встречаются в нужных нам фрагментах

Каждое «слово» в зависимости от того, в какую группу оно попало, представим в виде вектора значений из 6 чисел, содержащего 0 и 1 – 0, если число не относится к данной группе, 1 – если относится. Получается, что каждое «слово» закодировано пятью нулями и одной единицей. Каждый фрагмент, в котором мы будем искать стоимость, содержит 23 «слова», соответственно получаем 138 чисел для одного фрагмента. Именно эти значения будем подавать на вход нейронной сети.

Чтобы обучить нейронную сеть, составим выборку. Входные данные уже имеются, остается составить выходные. Выходные данные для одного фрагмента будут представлены в виде 23 чисел – 0 и 1. Единицами обозначим тот фрагмент, который в итоге нужно выбрать из текста и который содержит стоимость.

Как преобразовывались данные:

Фрагмент, содержащий стоимость:

['Цена', 'Договора', 'порядок', 'расчетов', '.', '2.1', '.', 'Стоимость', 'Объекта', 'составляет', '810', '000', '(', 'Восемьсот', 'десять', 'тысяч', ')', 'рублей', '.', 'Цена', 'является', 'окончательной', 'изменению']

Фрагмент, преобразованный в числа:

[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1]

Выходные данные для фрагмента:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]

Создаем модель нейронной сети. Она будет состоять из 4 плотно связанных слоев Dense, с учетом входного и выходного. Используем наиболее распространенную функцию активации «relu» для каждого слоя, кроме выходного. Также добавим слой Dropout, чтобы предотвратить переобучение модели.

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

model = keras.Sequential([ layers.Dense(138, activation='relu'), layers.Dense(138, activation='relu'), layers.Dropout(0.1), layers.Dense(23, activation='relu'), layers.Dense(23, activation='sigmoid')]) optimizer = tf.optimizers.Adam() model.compile(loss=tf.keras.losses.MSE, optimizer=optimizer, metrics=['accuracy']) history = model.fit(train_data, vyhod_data_train, epochs=1000)

Обучаем модель и выполняем предсказания для тестовых данных. На выходе получаем последовательность из 23 чисел для каждого фрагмента. Числа, которые больше 0,9 – нужные нам значения, заменим их на 1. Находим начало и конец последовательности единиц. Далее по индексам начала и конца последовательности единиц выбираем стоимость из фрагмента текста.

Что получается при обработке тестовых данных?

Фрагмент, в котором ищем стоимость

['Цена', 'Объекта‚', 'являющаяся', 'предметом', 'настоящего', 'Договора', ',', 'составляет', '2', '100', '000', '(', 'два', 'миллиона', 'сто', 'тысяч', ')', 'рублей', ',', 'именно', 'цена', 'земельного', 'участка']

Фрагмент, преобразованный в числа

[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1]

Значения, полученные после работы нейронной сети

[5.8360482e-14 5.7737207e-19 7.7303122e-18 6.7739243e-18 5.8828078e-14 1.1499115e-18 2.8125218e-13 9.2836785e-08 1.0000000e+00 1.0000000e+00 1.0000000e+00 1.0000000e+00 1.0000000e+00 1.0000000e+00 1.0000000e+00 1.0000000e+00 1.0000000e+00 1.0000000e+00 5.1083343e-10 2.7263733e-10 3.0139889e-14 2.2163703e-13 1.4157570e-14]

Выбранный фрагмент стоимости

['2', '100', '000', '(', 'два', 'миллиона', 'сто', 'тысяч', ')', 'рублей']

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

0
6 комментариев
Написать комментарий...
vadim negru

Интересно, что собой представляли 6% неугаданых фрагментов

Ответить
Развернуть ветку
NTA
Автор

Потерянные 6% фрагментов представляют собой неполные фрагменты или фрагменты, в которые попало лишнее слово или цифра.
Например, необходимо было выбрать фрагмент «3300 000 (три миллиона триста тысяч ) руб.», а нейронная сеть выбрала фрагмент «3300 000 (три миллиона триста тысяч ) руб. 2.2».

Ответить
Развернуть ветку
Эмилия Теплова

Обычная регулярка наверное будет быстрее и её так же можно дописать для 6% нераспознанных.

Ответить
Развернуть ветку
NTA
Автор

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

Ответить
Развернуть ветку
Андрей Гуртовой

Замечательно конечно, но кого вы тут собирались научить ? Если новичков то им будет не понятен смысл манипуляций как эта: "Следующим шагом нужно представить «слова» в виде чисел..."; а если старичков, то им будет не интересно т.к. этот материал в более подробных формах существует в изобилии. Извините если обижу, но именно по этой причине людей минусуют на хабрах, т.к. ничего нового не рассказывают.

 Лучше бы для новичков разжевали разбив на микрозадачи и предоставив подробные значения  до и после манипуляций.

Ответить
Развернуть ветку
NTA
Автор

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

Ответить
Развернуть ветку
3 комментария
Раскрывать всегда