Работа с NLP-моделями Keras в браузере с TensorFlow.js

nlp javascript

Этот туториал для тех, кто знаком с основами JavaScript и основами глубокого обучения для задач NLP (RNN, Attention). Если вы плохо разбираетесь в RNN, я рекомендую вам прочитать «Необоснованную эффективность рекуррентных нейронных сетей» Андрея Карпати.

Перевод статьи «NLP Keras model in browser with TensorFlow.js», автор — Mikhail Salnikov, ссылка на оригинал — в подвале статьи.

В этой статье я попытаюсь охватить три вещи:

  1. Как написать простую Named-entity recognition (NER) модель — типичная задача NLP.
  2. Как экспортировать эту модель в формат TensorFlow.js.
  3. Как сделать простое веб-приложение для поиска именованных объектов в строке без серверной части.

TensorFlow.js — это библиотека JavaScript для разработки и обучения ML-моделей на JavaScript, а также для развертывания в браузере или на Node.js.

В этом примере мы будем использовать простую модель Keras для решения классической задачи NER. Мы будем тренироваться на датасете CoNLL2003 . Наша модель — это просто векторное представление слов, GRU и очень простой механизм внимания. После мы визуализируем вектор внимания в браузере. Если вы знакомы с современными подходами для решения аналогичной задачи, вы знаете, что этот подход не является state-of-the-art подходом. Однако для запуска его в браузере этого вполне достаточно.

Задача и данные

В зависимости от вашего опыта, вы могли слышать об этом под разными названиями — тегирование последовательностей (sequence tagging), Part-of-Speech тегирование или, как в нашей задаче, распознавание именованных объектов (Named-entity recognition).

Обычно задача NER — это задача seq2seq. Для каждого x_i мы должны предсказать y_i, где x — входная последовательность, а y — последовательность именованных объектов.

В этом примере мы будем искать людей (B-PER, I-PER), местоположения (B-LOC, I-LOC) и организации (B-ORG, I-ORG). Кроме того, в модели будут определены специальные сущности, MISC — именованные сущности, которые не являются личностями, местами или организациями.

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

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

def word_preprocessor(word):
    word = re.sub(r'\d+', '1', re.sub(r"[-|.|,|\?|\!]+", '', word))
    word = word.lower()
    if word != '':
        return word
    else:
        return '.'

    
def load_data(path, word_preprocessor=word_preprocessor):
    tags = []
    words = []
    data = {'words': [], 'tags': []}
    with open(path) as f:
        for line in f.readlines()[2:]:
            if line != '\n':
                parts = line.replace('\n', '').split(' ')
                words.append(word_preprocessor(parts[0]))
                tags.append(parts[-1])
            else:
                data['words'].append(words)
                data['tags'].append(tags)
                words, tags = [], []

    return data

Как вы знаете, нейронные сети не могут работать со словами, только с числами. Вот почему мы должны представлять слова в виде чисел. Это не сложная задача, мы можем перечислить все уникальные слова и записать номер каждого слова вместо него самого. Для хранения цифр и слов мы можем составить словарь. Этот словарь должен поддерживать слова «неизвестный» (UNK) для ситуации, когда мы будем делать прогноз для новой строки со словами, которых нет в словаре. Также словарь должен содержать слово «дополнено» (PAD). Для нейронной сети все строки должны иметь одинаковый размер, поэтому, когда одна строка будет меньше другой, мы заполним пробелы этим словом.

def make_vocab(sentences, tags=False):
    vocab = {"(PAD)": PAD_ID, "(UNK)": UNK_ID}
    idd = max([PAD_ID, UNK_ID]) + 1
    for sen in sentences:
        for word in sen:
            if word not in vocab:
                vocab[word] = idd
                idd += 1
                
    return vocab

Далее, давайте напишем простой помощник для перевода предложений в последовательность чисел.

def make_sequences(list_of_words, vocab, word_preprocessor=None):
    sequences = []
    for words in list_of_words:
        seq = []
        for word in words:
            if word_preprocessor:
                word = word_preprocessor(word)
            seq.append(vocab.get(word, UNK_ID))
        sequences.append(seq)
    return sequences

Как вы можете увидеть выше, мы должны дополнить последовательности для работы с нейронной сетью, для этого вы можете использовать внутренний метод Keras — pad sequence.

train_X = pad_sequences(train_data['words_sequences'],
                        maxlen=MAX_SEQUENCE_LENGTH,
                        value=PAD_ID, padding='post',
                        truncating='post')
valid_X = pad_sequences(valid_data['words_sequences'],
                        maxlen=MAX_SEQUENCE_LENGTH,
                        value=PAD_ID,
                        padding='post',
                        truncating='post')

train_y = pad_sequences(train_data['tags_sequences'],
                        maxlen=MAX_SEQUENCE_LENGTH,
                        value=PAD_ID,
                        padding='post',
                        truncating='post')
valid_y = pad_sequences(valid_data['tags_sequences'],
                        maxlen=MAX_SEQUENCE_LENGTH,
                        value=PAD_ID,
                        padding='post',
                        truncating='post')

Модель

Дай угадаю.. RNN?

Верно, она самая. Если точнее, это GRU с простым слоем внимания. Для представления слов используется GloVe. В этом посте я не буду вдаваться в подробности, а просто оставлю здесь код модели. Я надеюсь, что это легко понять.

from tensorflow.keras.layers import (GRU, Dense, Dropout, Embedding, Flatten,
                                     Input, Multiply, Permute, RepeatVector,
                                     Softmax)
from tensorflow.keras.models import Model

from utils import MAX_SEQUENCE_LENGTH


def make_ner_model(embedding_tensor, words_vocab_size, tags_vocab_size,
                   num_hidden_units=128, attention_units=64):
    EMBEDDING_DIM = embedding_tensor.shape[1]

    words_input = Input(dtype='int32', shape=[MAX_SEQUENCE_LENGTH])

    x = Embedding(words_vocab_size + 1,
                    EMBEDDING_DIM,
                    weights=[embedding_tensor],
                    input_length=MAX_SEQUENCE_LENGTH,
                    trainable=False)(words_input)

    outputs = GRU(num_hidden_units,
                    return_sequences=True,
                    dropout=0.5,
                    name='RNN_Layer')(x)

    # Simple attention
    hidden_layer = Dense(attention_units, activation='tanh')(outputs)
    hidden_layer = Dropout(0.25)(hidden_layer)
    hidden_layer = Dense(1, activation=None)(hidden_layer)
    hidden_layer = Flatten()(hidden_layer)
    attention_vector = Softmax(name='attention_vector')(hidden_layer)
    attention = RepeatVector(num_hidden_units)(attention_vector)
    attention = Permute([2, 1])(attention)
    encoding = Multiply()([outputs, attention])

    encoding = Dropout(0.25)(encoding)
    ft1 = Dense(num_hidden_units)(encoding)
    ft1 = Dropout(0.25)(ft1)
    ft2 = Dense(tags_vocab_size)(ft1)
    out = Softmax(name='Final_Sofmax')(ft2)

    model = Model(inputs=words_input, outputs=out)
    return model

После построения модели, мы должны скомпилировать, обучить и сохранить ее. Вы можете догадаться, что для запуска этой модели в браузере мы должны сохранять не только веса для нее, но также описание модели и словари для слов и тегов. Давайте определим метод для экспорта модели и словарей в формат, поддерживаемый JavaScript (в основном JSON):

import json
import os
import tensorflowjs as tfjs

def export_model(model, words_vocab, tags_vocab, site_path):
    tfjs.converters.save_keras_model(
        model,
        os.path.join(site_path, './tfjs_models/ner/')
        )

    with open(os.path.join(site_path, "./vocabs.js"), 'w') as f:
        f.write('const words_vocab = {\n')
        for l in json.dumps(words_vocab)[1:-1].split(","):
            f.write("\t"+l+',\n')
        f.write('};\n')
        
        f.write('const tags_vocab = {\n')
        for l in json.dumps(tags_vocab)[1:-1].split(","):
            f.write("\t"+l+',\n')
        f.write('};')
    print('model exported to ', site_path)

Наконец, давайте скомпилируем, обучим и экспортируем модель:

model.compile(
    loss='categorical_crossentropy',
    optimizer='Adam',
    metrics=['categorical_accuracy']
    )
model.fit(train_X, train_y,
          epochs=args.epoches,
          batch_size=args.batch_size,
          validation_data=(valid_X, valid_y))

export_model(model, words_vocab, tags_vocab, args.site_path)

Полный код для этих шагов вы можете найти в моем репозитории на GitHub в train.py.

Разработка веб-приложения

Итак, модель готова, и теперь мы должны начать разработку веб-приложения для проверки нашей модели режима в браузере. Нам нужно настроить рабочую среду. В принципе, не важно, как вы будете хранить свою модель, веса и словарь, но в качестве примера я покажу вам мое простое решение — локальный сервер node.js.

Нам потребуется два файла: package.json и server.js.

Package.json:

{
    "name": "tfjs_ner",
    "version": "1.0.0",
    "dependencies": {
        "express": "latest"
    }
}

Server.js:

let express = require("express")
let app = express();

app.use(function(req, res, next) {
    console.log(`${new Date()} - ${req.method} request for ${req.url}`);
    next();    
});

app.use(express.static("./static"));

app.listen(8081, function() {
    console.log("Serving static on http://localhost:8081");
});

В server.js мы определили статическую папку для хранения модели, js-скриптов и всех других файлов. Для использования этого сервера, вы должны ввести

npm install && node server.js

в вашем терминале. После этого вы можете получить доступ к своим файлам в браузере по адресу http://localhost:8081.

Далее перейдем к основной части веб-приложения. Существуют index.htmlForex.js и файлы, которые были созданы на предыдущем шаге. Как видите, это очень маленькое веб-приложение. index.html содержит требования и поле для ввода строк пользователем.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
    <main role="main">
        <form class="form" onkeypress="return event.keyCode != 13;">
            <input type="text" id='input_text'>
            <button type="button" id="get_ner_button">Search Entities</button>
        </form>

        <div class="results">
            
        </div>
    </main>

    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0/dist/tf.min.js"></script>
    <script src="vocabs.js"></script>
    <script src="predict.js"></script>
</body>
</html>

Теперь самая интересная часть туториала — про TensorFlow.js. Вы можете загрузить модель с помощью метода tf.loadLayersModel с использованием оператора await. Это важно, потому что мы не хотим блокировать наше веб-приложение при загрузке модели. Если мы загрузим модель, то у нас будет модель, которая может предсказывать только токены, но как насчет вектора внимания? Для получения данных из внутренних слоев в TensorFlow.js мы должны создать новую модель, в которой выходные слои будут содержать выходные данные и другие слои из исходной модели, например:

let model, emodel;
(async function() {
    model = await tf.loadLayersModel('http://localhost:8081/tfjs_models/ner/model.json');
    let outputs_ = [model.output, model.getLayer("attention_vector").output];
    emodel = tf.model({inputs: model.input, outputs: outputs_});
})();

Здесь model — это оригинальная модель, emodel — модель с attention_vector на выходе.

Предобработка

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

const MAX_SEQUENCE_LENGTH = 113;

function word_preprocessor(word) {
  word = word.replace(/[-|.|,|\?|\!]+/g, '');
  word = word.replace(/\d+/g, '1');
  word = word.toLowerCase();
  if (word != '') {
    return word;
  } else {
    return '.'
  }
};

function make_sequences(words_array) {
  let sequence = Array();
  words_array.slice(0, MAX_SEQUENCE_LENGTH).forEach(function(word) {
    word = word_preprocessor(word);
    let id = words_vocab[word];
    if (id == undefined) {
      sequence.push(words_vocab['']);
    } else {
      sequence.push(id);
    }  
  });

  // pad sequence
  if (sequence.length < MAX_SEQUENCE_LENGTH) {
    let pad_array = Array(MAX_SEQUENCE_LENGTH - sequence.length);
    pad_array.fill(words_vocab['']);
    sequence = sequence.concat(pad_array);
  }

  return sequence;
};

Предсказания

Теперь мы должны обеспечить передачу данных из простого текстового формата строки в формат TF — тензор. В предыдущем разделе мы написали помощника для перевода строки в последовательность чисел. Теперь мы должны создать tf.tensor из этого массива. Как вы помните, входной слой модели имеет форму (None, 113) , поэтому мы должны расширить размер входного тензора. Ну вот и все, теперь мы можем делать предсказание в браузере методом  .predict. После этого вам нужно вывести прогнозируемые данные в браузере, и ваше веб-приложение с нейронной сетью без серверной части готово.

const getKey = (obj,val) => Object.keys(obj).find(key => obj[key] === val); // For getting tags by tagid

$("#get_ner_button").click(async function() {
  $(".results").html("word - tag - attention</br><hr&gt;");

  let words = $('#input_text').val().split(' ');
  let sequence = make_sequences(words);
  let tensor = tf.tensor1d(sequence, dtype='int32').expandDims(0);
  let [predictions, attention_probs] = await emodel.predict(tensor);
  attention_probs = await attention_probs.data();
  
  predictions = await predictions.argMax(-1).data();
  let predictions_tags = Array();
  predictions.forEach(function(tagid) {
    predictions_tags.push(getKey(tags_vocab, tagid));
  });

  words.forEach(function(word, index) {
    $(".results").append(word+' - '+predictions_tags[index]+' - '+attention_probs[index]+'');
  });
});

Итог

TensorFlow.js — это библиотека для использования нейронных сетей в браузерах, таких как Chrome, Firefox или Safari. Если вы откроете это веб-приложение на смартфоне iPhone или Android, оно тоже будет работать.

Вы можете найти код этого приложения с некоторыми дополнениями на моем GitHub.

tensorflow 2.0

Google объявила о выходе TensorFlow 2.0

Google анонсировала TensorFlow 2.0 — новую версию программной библиотеки для создания моделей машинного обучения. Компания опубликовала подробный пост с описанием обновлений фреймворка. Итак, какие улучшения предлагает TensorFlow 2.0? Новая архитектура С момента создания в TensorFlow интегрируется ряд приложений и библиотек (смотри рисунок ниже). Теперь…
pytorch tensorflow сходства и отличия

PyTorch и TensorFlow: отличия и сходства фреймворков

В статье будет рассказано о главных сходствах и различиях между двумя популярными фреймворками глубокого обучения — PyTorch и TensorFlow. Почему такой выбор библиотек? Существует много фреймворков глубокого обучения, многие из которых жизнеспособны, но я выбрал только PyTorch и TensorFlow, так как интересно сравнить эти…

Обучение Inception-v3 распознаванию собственных изображений

В моем предыдущем посте мы увидели, как выполнять распознавание изображений с помощью TensorFlow с использованием API Python на CPU без какого-либо обучения. Мы использовали предобученную модель Inception-v3, которую Google уже обучил на тысяче классов, но что, если мы хотим сделать то же самое, но…

Нейросеть Pet Detector присылает SMS, если кот хочет домой. Код доступен на Github

Что делать, если ваш кот не выражает свое желание выйти за дверь громким мяуканьем и часами ждет под дверью, пока вы его не заметите? Для решения этой проблемы разработчик EdgeElectronics создал Pet Detector — нейронную сеть, которая присылает уведомление хозяину на смартфон, если кот находится…
tensorflow mobile туториал

TensorFlow для мобильных устройств на Android и iOS

TensorFlow обычно используется для тренировки масштабных моделей на большом наборе данных, но нельзя игнорировать развивающийся рынок смартфонов и необходимость создавать будущее, основанное на глубоком обучении. Перед вами перевод статьи TensorFlow on Mobile: Tutorial, автор — Sagar Sharma. Ссылка на оригинал — в подвале статьи.  Те, кто не…
туториал распознавание изображений tensorflow

Распознавание изображений предобученной моделью Inception-v3 c Python API на CPU

Это самый быстрый и простой способ реализовать распознавание изображений на ноутбуке или стационарном ПК без какого-либо графического процессора, потому что это можно сделать лишь с помощью API, и ваш компьютер отлично справится с этой задачей. Перед вами перевод статьи TensorFlow Image Recognition Python API Tutorial,…
vgg16 нейронная сеть

VGG16 — сверточная сеть для выделения признаков изображений

VGG16 — модель сверточной нейронной сети, предложенная K. Simonyan и A. Zisserman из Оксфордского университета в статье “Very Deep Convolutional Networks for Large-Scale Image Recognition”. Модель достигает точности 92.7% — топ-5, при тестировании на ImageNet в задаче распознавания объектов на изображении. Этот датасет состоит…

FaceNet — пример простой системы распознавания лиц с открытым кодом Github

Распознавание лица — последний тренд в авторизации пользователя. Apple использует Face ID, OnePlus — технологию Face Unlock. Baidu использует распознавание лица вместо ID-карт для обеспечения доступа в офис, а при повторном пересечении границы в ОАЭ вам нужно только посмотреть в камеру. В статье разбираемся,…

Нейросеть диагностирует болезнь Альцгеймера с точностью 94%

Согласно данным Alzheimer’s Association, только в США болезнью Альцгеймера страдают 5,7 миллиона человек. В эту цифру входит 5,5 миллионов больных старше 65 лет, и 200 000 человек моложе 65 с ранними симптомами болезни. Учёные из Стэнфорда разработали алгоритм, который поможет врачам в ранней диагностике…
Глубокое обучение с Tensorflow

TensorFlow туториал. Часть 4: глубокое обучение

Теперь, когда вы изучили и подготовили свои данные, пришло время создать архитектуру нейронной сети с помощью пакета TensorFlow! Предыдущие статьи: TensorFlow туториал. Часть 1: тензоры и векторы TensorFlow туториал. Часть 2: установка и начальная настройка TensorFlow туториал. Часть 3: работа с данными Моделирование нейронной…
tensorflow анализ и работа с данными

TensorFlow туториал. Часть 3: работа с данными

Пора переходить к работе с реальными данными! Мы будем работать с дорожными знаками Бельгии. Дорожный трафик — понятная тема, но не помешает уточнить, какие данные включены в датасет, перед тем как приступить к программированию. Перед прочтением статьи рекомендуем изучить: TensorFlow туториал. Часть 1: тензоры…
установка Tensorflow

TensorFlow туториал. Часть 2: установка и начальная настройка

Теперь, когда вы получше узнали TensorFlow, пора приступить к работе с ним и установить библиотеку. Важно знать, что TensorFlow предоставляет API для Python, C ++, Haskell, Java, Go, Rust. Также существует сторонний пакет для R. Читайте также Часть 1: тензоры и векторы После прочтения этого туториала…
Tensorflow tutorial

TensorFlow туториал. Часть 1: тензоры и векторы

TensorFlow — это ML-framework от Google, который предназначен для проектирования, создания и изучения моделей глубокого обучения. Глубокое обучение — это область машинного обучения, алгоритмы в которой были вдохновлены структурой и работой мозга. Вы можете использовать TensorFlow, чтобы производить численные вычисления. Само по себе это…
введение в машинное обучение примеры и модели для новичков

Введение в машинное обучение

Перевод статьи разработчика алгоритмов машинного обучения, бизнес-консультанта и популярного автора Ганта Лаборде «Machine Learning: from Zero to Hero».  Начнешь c “Зачем?”, придешь к “Я готов!” Если вы мало знаете об основах машинного обучения, то эта статья как раз для вас. Я буду постепенно излагать…
tensprflow detection api

Google выпустили дополнения к Tensorflow Object Detection API

Обновления включают новые модели класса SSD, которые оптимизированы для ускоренного обучения на облачных TPU, и готовые веса для них. Теперь обучение модели RetinaNet на основе ResNet-50 на датасете COCO для достижения 35% mAP занимает 3,5 часа. Также поддерживается ускоренный вывод через квантование и упрощённый…
tensorflow hub библиотека

Введение в TensorFlow Hub: библиотеку модулей машинного обучения для TensorFlow

Репозиторий с общим кодом — одна из фундаментальных идей в разработке программного обеспечения. Библиотеки делают программистов гораздо более эффективными. В некотором смысле они даже меняют сам процесс решения проблем программирования. Как выглядит идеальная библиотека с точки зрения разработчика алгоритмов машинного обучения? Мы хотим делиться предобученными моделями.…
Heatmap and offset vector simplification

Pose estimation в реальном времени прямо в браузере с TensorFlow.JS

Что же такое определение позы (pose estimation)? Определение позы — это метод компьютерного зрения, который оценивает фигуры людей на изображениях и видео и определяет, где находятся основные суставы. Технология не распознает, кто конкретно представлен на изображении — нет идентифицирующей личность информации. У pose estimation много областей применения, например, дополненная реальность, дорисованная анимация,…
нейросеть nvidia удаляет шум с фото

Нейросеть от Nvidia научилась удалять шум с фотографий

Nvidia представили новый алгоритм Noise2Noise, который за несколько секунд очищает фотографию от артефактов, шумов, текста, и автоматически улучшает её. Метод подходит для реконструкции МРТ-снимков и фотографий, сделанных при плохом освещении, т.е. в случаях когда не существует «чистого» исходного изображения. Нейросеть обучили на базе 50…
Tensorflow Javascript

Обзор Tensorflow.JS: машинное обучение на JavaScript

Разработчики Google представили TensorFlow.JS — библиотеку с открытым исходным кодом, которую можно использовать для определения, обучения и запуска моделей машинного обучения полностью в браузере, используя Javascript и высокоуровневое API. Если вы Javascript-разработчик, который новичок в машинном обучении, TensorFlow.js — отличный способ начать обучение. Если вы ML-инженер и новичок в Javascript,…
really simple captcha

Пример взлома капчи за 15 минут с помощью машинного обучения

А именно самого используемого в мире плагина для WordPress Really Simple Captcha. Капча (CAPTCHA) — надоедливая картинка с текстом, который надо ввести, чтобы попасть на сайт. Капчу придумали чтобы роботы не могли автоматически заполнять формы, и чтобы владельцы сайта были уверены, что пользователь — человек. С развитием машинного обучения…