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

Задача: найти в тексте описание стоимости недвижимости, то есть численное обозначение и стоимость, записанную прописью. Например, 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. Уберем стоп-слова из текста и знаки препинания, которые нам не нужны (например, : или “).

words = [word for word in words if not word in new_stopwords]

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 групп значений:

  • PNCT – знаки пунктуации: ( ) . ,

  • NUMB – числа

  • NUMR – числительные

  • NOUN – существительные: «тысяча», «миллион», «миллиард»

  • NOUN – существительные: «рубль», «копейка» и их сокращенные формы

  • Все остальные части речи, которые не встречаются в нужных нам фрагментах

Каждое «слово» в зависимости от того, в какую группу оно попало, представим в виде вектора значений из 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% по результатам работы данной нейронной сети.

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


  1. saluev
    06.09.2021 15:24
    +8

    Применение машинного обучения как жанр комедии)


  1. Andrey_Solomatin
    06.09.2021 16:09
    +5

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

    1) Устанавливаем библиотеки 2) Рисуем остаток совы.


  1. Kolonist
    06.09.2021 16:24
    +3

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


    1. amarao
      06.09.2021 16:31
      +6

      Кстати, интересная идея. Хорошо тренированная нейронная модель на 100 входов и 100 выходов и 110 слоёв должна будет справиться с массивом на 100 элементов.


      1. Vadimatorikda
        06.09.2021 17:33
        +4

        Отвратительно))) Продолжай.


      1. sshikov
        06.09.2021 20:34

        А вот представьте себе, нейронка, которая выбирает оптимальный метод сортировки по какому-то нечеткому критерию. Ну т.е. методы заранее запрограммированы, а вот выбор одного из них — на усмотрение сетки. И почему нет?


        1. amarao
          06.09.2021 21:08
          +1

          Да без проблем. Даже нейронка не нужна. Берём все алгоритмы сортировки, выбираем оптимальный для заданного набора входных данных, получаем просто чудесное время сортировки. Если игнорировать затраты на выбор, то можно будет побить теоретический предел.


      1. sunsexsurf
        07.09.2021 08:49

        А вы хорош )


    1. x67
      06.09.2021 19:52

      И тестовое задание - распишите архитектуру нейросети, которая будет способна отсортировать массив методом пузырька


  1. goldrobot
    08.09.2021 08:02

    Процент корректно выбранных фрагментов стоимости составляет 94%

    Ну нафиг такую регулярку.