Учебник по нейронной сети для художников

Ссылка на оригинал - http://blog.otoro.net/2017/01/01/recurrent-neural-network-artist/, автор публикации - http://blog.otoro.net

 

Этот пост не является исчерпывающим обзором текущих нейронных сетей. Он предназначен для читателей без каких-либо навыков машинного обучения. Цель состоит в том, чтобы показать художникам и дизайнерам, как использовать предварительно обученную нейронную сеть для создания интерактивных цифровых работ с использованием простой библиотеки Javascript и p5.js.

Вступление

Поколение почерка с Javascript

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

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

Почерк - это форма эскиза. Недавно я сотрудничал с Шаном Картером , Яном Джонсоном и Крисом Олахом, чтобы опубликовать пост на distill.pub.на почерк поколения. В частности, эксперименты в посте помогают визуализировать внутреннюю часть рекуррентной нейронной сети, обученной генерировать почерк. Правда в том, что этот проект также служил для меня своего рода мета-экспериментом. Вместо того, чтобы напрямую работать над экспериментами по визуализации и надписью, я решил создать предварительно обученную модель рукописного ввода с простым в использовании интерфейсом Javascript, и пусть мои сотрудники, которые являются талантливыми художниками по визуализации данных, экспериментируют с моделью, чтобы создать что-то из этого. В итоге они создали прекрасные эксперименты по интерактивной визуализации в посте distill.pub .

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

Моделирование почерка мозга

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

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

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

С помощью RNN мы можем хранить этот тип неопределенного знания непосредственно в нейронах RNN, и мы называем этот объект скрытым состоянием RNN. Это скрытое состояние является просто вектором чисел с плавающей запятой, которые отслеживают, насколько активен каждый нейрон. То, что наша модель напишет дальше, будет зависеть от ее скрытого состояния. Этот скрытый объект состояния будет обновляться после того, как что-то написано, поэтому он будет постоянно меняться. Мы продемонстрируем, как это работает, в следующем разделе.

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

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

  state_diagram

Модель порождающей последовательности

Не беспокойтесь, если вы не полностью понимаете эту схему. В следующем разделе мы покажем, что происходит построчно с Javascript.

Рекуррентная нейронная сеть для почерка

Мы предварительно обучили рекуррентную модель нейронной сети, чтобы выполнить задачу почерка, описанную в предыдущем разделе. В этом разделе мы опишем , как использовать эту модель в Javascript с p5.js . Ниже приведен весь эскиз p5.js для создания почерка.

var x, y;
var dx, dy;
var pen;
var prev_pen;
var rnn_state;
var pdf;
var temperature = 0.65;
var screen_width = window.innerWidth;
var screen_height = window.innerHeight;
var line_color;

function restart() {
  x = 50;
  y = screen_height/2;
  dx = 0;
  dy = 0;
  prev_pen = 0;
  rnn_state = Model.random_state();
  line_color = color(random(255), random(255), random(255))
}

function setup() {
  restart();
  createCanvas(screen_width, screen_height);
  frameRate(60);
  background(255);
  fill(255);
}

function draw() {
  rnn_state = Model.update([dx, dy, prev_pen], rnn_state);
  pdf = Model.get_pdf(rnn_state);
  [dx, dy, pen] = Model.sample(pdf, temperature);
  if (prev_pen == 0) {
    stroke(line_color);
    strokeWeight(2.0);
    line(x, y, x+dx, y+dy);
  }
  x += dx;
  y += dy;
  prev_pen = pen;
  if (x > screen_width - 50) {
    restart();
    background(255);
    fill(255);
  }
}

Мы объясним, как работает каждая строка. Во-первых, нам нужно определить несколько переменных, чтобы отслеживать, где на самом деле находится перо ( x, y). Наша модель будет работать с меньшими смещениями координат ( dx, dy) и определять, куда пера пойдет дальше, и (x, y)будет накопление (dx, dy).

var x, y; // absolute coordinates of where the pen is
var dx, dy; // offsets of the pen strokes, in pixels

Кроме того, наша ручка не всегда будет касаться бумаги. Нам нужна переменная с именем pen, чтобы смоделировать это. Если penноль, то наша ручка касается бумаги на текущем временном шаге. Нам также нужно отслеживать penпеременную на предыдущем временном шаге и сохранять ее в prev_pen.

// keep track of whether pen is touching paper. 0 or 1.
var pen;
var prev_pen; // pen at the previous timestep

Если у нас есть список (dx, dy, pen)переменных, генерируемых нашей моделью на каждом временном шаге, нам будет достаточно использовать эти данные, чтобы нарисовать то, что модель сгенерировала на экране. В начале все эти переменные ( dx, dy, x, y, pen, prev_pen) будут инициализированы в ноль.

Мы также определим некоторые переменные объекты, которые будут использоваться нашей моделью RNN:

var rnn_state; // store the hidden states the rnn

// store all the parameters of a mixture-density distribution
var pdf;

// controls the amount of uncertainty of the model
// the higher the temperature, the more uncertainty.
var temperature = 0.65; // a non-negative number.

Как описано в предыдущем разделе, rnn_stateпеременная будет представлять скрытое состояние RNN. Эта переменная будет содержать все смутные представления о том, что RNN думала, что она написала в прошлом. Чтобы обновить rnn_state, мы будем использовать updateфункцию в модели позже в коде.

rnn_state = Model.update([dx, dy, prev_pen], rnn_state);

Объект rnn_stateбудет использоваться для генерации распределения вероятностей того, что модель напишет дальше. Это распределение вероятностей будет представлено как названный объект pdf. Чтобы получить pdfобъект из rnn_state, мы будем использовать get_pdfфункцию позже, например так:

pdf = Model.get_pdf(rnn_state);

Дополнительная переменная называется temperatureпозволяет нам контролировать, насколько уверенно или неопределенно мы хотим сделать модель. В сочетании с pdfобъектом мы можем использовать sampleфункцию в модели для выборки следующего набора (dx, dy, pen)значений из нашего распределения вероятностей. Позже мы будем использовать следующую функцию:

[dx, dy, pen] = Model.sample(pdf, temperature);

Единственные другие переменные, которые нам нужны, - это управление цветом почерка, а также отслеживание размеров экрана браузера:

// stores the browser's dimensions
var screen_width = window.innerWidth;
var screen_height = window.innerHeight;

// colour for the handwriting
var line_color;

Теперь мы готовы инициализировать все эти переменные, которые мы только что объявили для фактического создания рукописного ввода. Мы создадим функцию, вызываемую restartдля инициализации этих переменных, так как мы будем повторно инициализировать их много раз позже.

function restart() {
  // set x to be 50 pixels from the left of the canvas
  x = 50;
  // set y somewhere in middle of the canvas
  y = screen_height/2;

  // initialize pen's states to zero.
  dx = 0;
  dy = 0;
  prev_pen = 0;
  // note: we draw lines based off previous pen's state

  // randomise the rnn's initial hidden states
  rnn_state = Model.random_state();

  // randomise colour of line by choosing RGB values
  line_color = color(random(255), random(255), random(255))
}

После создания restartфункции мы можем определить обычную функцию p5.js setup для инициализации эскиза.

function setup() {
  restart(); // initialize variables for this demo
  createCanvas(screen_width, screen_height);
  frameRate(60); // 60 frames per second
  // clear the background to be blank white colour
  background(255);
  fill(255);
}

Наше поколение почерка будет происходить в drawфункции фреймворка p5.js. Эта функция вызывается 60 раз в секунду. Каждый раз, когда вызывается эта функция, RNN будет рисовать что-то на экране.

function draw() {

  // using the previous pen states, and hidden state
  // to get next hidden state 
  rnn_state = Model.update([dx, dy, prev_pen], rnn_state);

  // get the parameters of the probability distribution
  // from the hidden state
  pdf = Model.get_pdf(rnn_state);

  // sample the next pen's states
  // using our probability distribution and temperature
  [dx, dy, pen] = Model.sample(pdf, temperature);

  // only draw on the paper if pen is touching the paper
  if (prev_pen == 0) {
    // set colour of the line
    stroke(line_color);
    // set width of the line to 2 pixels
    strokeWeight(2.0);
    // draw line connecting prev point to current point.
    line(x, y, x+dx, y+dy);
  }

  // update the absolute coordinates from the offsets
  x += dx;
  y += dy;

  // update the previous pen's state
  // to the current one we just sampled
  prev_pen = pen;

  // if the rnn starts drawing close to the right side
  // of the screen, restart our demo
  if (x > screen_width - 50) {
    restart();
    // reset screen
    background(255);
    fill(255);
  }

}

В каждом кадре drawфункция обновляет скрытое состояние модели на основе того, что она ранее нарисовала на экране. Из этого скрытого состояния модель будет генерировать распределение вероятностей того, что будет сгенерировано следующим. Основываясь на этом распределении вероятностей, вместе с temperatureпараметром мы случайным образом выберем, какое действие он предпримет в форме нового набора (dx, dy, pen)переменных. Основываясь на этом новом наборе переменных, он нарисует линию на экране, если перо ранее касалось блокнота, и обновит глобальное местоположение пера. Как только глобальное местоположение пера приблизится к правой стороне экрана, оно сбросит эскиз и начнет заново.

Собрав все это вместе, мы получим следующий набросок почерка.

 

Так что у вас есть, поколение почерка в Вашем браузере с помощью нескольких строк JavaScript , используя p5.js .

Выборка из распределения вероятностей с изменяющейся температурой

Предполагается, что переменная pdfбудет хранить распределение вероятностей следующего хода пера на каждом временном шаге. Под капотом объект на pdfсамом деле просто содержит параметры сложного распределения вероятностей (т.е. средние значения и стандартные отклонения группы нормальных распределений). Мы решили смоделировать распределение вероятностей dxи dyкак распределение плотности смеси .

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

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

Когда мы берем это распределение вероятностей и делаем выборку из этого распределения, чтобы получить набор (dx, dy, pen)значений, чтобы определить, что рисовать дальше, мы используем temperatureпараметр для контроля уровня неопределенности модели. Если параметр температуры очень высок, то мы с большей вероятностью получим выборки в менее вероятных областях распределения вероятностей. Если параметр температуры очень низкий или близок к нулю, то мы будем получать выборки только из наиболее вероятных частей распределения.

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

Визуализируйте распределение плотности смеси, регулируя температуру.

 

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

 

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

Расширение демонстрации почерка

Одним из наиболее интересных аспектов сочетания машинного обучения с дизайном является изучение взаимодействия человека и машины. Типичная структура машинного обучения + стек Python затрудняет развертывание действительно интерактивных веб-приложений, поскольку для них часто требуется, чтобы на стороне сервера были написаны специальные веб-службы для обработки взаимодействия пользователя на стороне клиента. Приятной особенностью Javascript-фреймворков, таких как p5.js, является то, что интерактивное программирование может быть легко выполнено и развернуто без особых усилий в веб-браузере.

 

Возможное интерактивное расширение, которое мы можем построить из базовой демонстрации рукописного ввода, состоит в том, чтобы пользователь в интерактивном режиме выводил некоторый почерк на экран, а когда пользователь бездействует, пусть модель непрерывно прогнозирует остальную часть образца рукописного ввода. Еще одно расширение, которое мы можем построить, аналогично тому, которое мы публикуем в сообщении distill.pub , состоит в том, чтобы образец модели имел несколько возможных путей, которые следуют пути рукописного ввода, созданному пользователем.

Есть бесчисленное множество других возможностей, которые можно поэкспериментировать с этой моделью. Также будет интересно объединить эту модель с более продвинутыми фреймворками, такими как paper.js или d3.js, чтобы генерировать лучше выглядящие штрихи.

Используйте этот код!

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

Этот пост только царапает поверхность повторяющихся нейронных сетей. Если вы хотите более активно участвовать в процессе разработки машинного обучения и обучать своим собственным моделям, есть отличные ресурсы, чтобы узнать, как создавать модели с помощью TensorFlowили keras . Если вы используете keras для создания и обучения своих моделей, есть даже инструмент под названием keras.js, который позволяет экспортировать предварительно обученные модели для использования в веб-браузере, поэтому вы можете создавать интерфейсы моделей, такие как модель рукописного ввода Javascript, используемая в этом посте. Я лично не использовал keras.js , и мне было интересно просто написать модель рукописного ввода с нуля в Javascript.

Обновить:

Эта модель уже была портирована на bl.ocks , дополненная несколько людей , чтобы сделать некоторые очень , интересные , вещи .

Не нашли ответ на свой вопрос? Возможно, вы найдете решение проблемы на нашем канале в Youtube! Здесь мы собрали небольшие, но эффективные инструкции. Смотрите и подписывайтесь на наш youtube-канал!

Смотреть на Youtube