艺术家如何应用RNN(循环神经网络)创作AI化的艺术作品

RitterLiu 2017-01-10

艺术家如何应用RNN(循环神经网络)创作AI化的艺术作品

文章导读:这篇文章不是为了全面深入的介绍循环神经网络(recurrent neural networks),而是为那些没有任何机器学习(machine learning)背景知识的读者提供一种思路,意在展示艺术家和设计师运用简单的Javascript和p5.js库构造预训练神经网络、进而创作出交互式数字作品的过程。

引言

艺术家如何应用RNN(循环神经网络)创作AI化的艺术作品

近年来,对于那些富有创造性的群体来说,机器学习已经成为一种流行的工具。风格迁移(style transfer)、t-sne算法、自编码器(autoencoders)、生成对抗网络(GAN)等技术以及其他众多的方法,均已成功进入数字大师们的工具箱。

其中,许多技术都在利用卷积神经网络(convolutional neural networks)进行特征提取和特征处理。

另一方面,由于循环神经网络(RNNs)以及其他的自回归模型(autoregressive models),数款强大的工具已经能够生成现实的序列数据。艺术家们就是运用的这种技术来生成相关的文本、音乐和声音。

当下,我认为还没有引起足够重视的其中一个领域在于矢量图形的生成方面,而这可能因为缺乏可用数据的缘故。

书法是素描艺术的一种形式。最近我与山·卡特(ShanCarter)、 伊安·约翰逊(Ian Johnson)以及克里斯·欧拉(ChrisOlah)等工程师共同合作,计划在distill.pub上发表一篇关于手写体生成的文章。在此特别值得一提的是,训练后用于生成手写体的循环神经网络(RNNs),已经在文中实验的帮助下实现了内部构造的可视化过程。

事实上,该实验也是我自己的其中一个实验。我没有直接开展可视化实验和书写生成方面的工作,而是创建了一个预训练书写模型和一个简单易用的Javascript接口。与此同时,我还与数位才华横溢的数据可视化专家相互合作,应用该模型进行实验以创造出某些有价值的东西。最终,他们开发出了非常出色的交互式可视化实验。

我决定写这篇文章,同时提供与 distill.pub项目中使用的相同的书写生成模型并附上相关解说,希望其他艺术家和设计师将来也能利用这些技术。如果可能的话,也希望艺术家和设计师们能在该领域上有更加深入的了解。

模拟大脑书写

即使是在写信的过程中,我们的大脑也在处理很多事情。就我们所发出的“书写”动作而言,我们的大脑在这个过程中需要先制定相关的计划,策划要写的内容,选择合适的词汇并思考好书写的整洁度,然后我们才开始执笔在纸上写东西。写东西的时候,我们的大脑还需要决定从哪里落笔、怎样运笔以及什么时候收笔。

以上写信的整个过程,很难通过某个Javascript模型模拟出来。但是我们可以尝试通过模拟书写过程的最后一部分,即从哪里落笔、怎样运笔以及什么时候收笔,来建立类似的书写过程模型。因此,我们的书写过程的模型在这里只关注笔的位置和笔是否接触到记事本。

针对该书写过程模型,我们有两个假设。第一个假设是,模型接下来要写的内容只取决于过去所写的内容。然而,当我们写东西的时候,即使我们还准确记得上一笔画的具体细节,也并不能清楚地记得许多笔画之前的细节,对于之前所写的内容只能有一个模糊的印象。这模糊的印象事实上可以在循环神经网络(recurrentneural network)的语境中进行模拟。

我们可以将这一类模糊的印象直接存储到某个循环神经网络(RNN),并将其称作RNN的隐藏状态(hiddenstate)。这个隐藏状态相当于浮点数矢量,用于跟踪每个神经元的活跃状态。

因此,我们的模型接下来要写的内容将取决于其隐藏状态。当某一部分的写作过程完成后,这个隐藏状态对象还会持续更新,不断处于变化中。在下一部分中,我们将会展示其工作原理。

关于书写生成的第二个假设是,模型不能完全确定接下来应该写什么。事实上,模型下一步会写什么的决策是随机的。例如,当模型编写字母y的时候,它有可能会继续书写字母y,只是使其底钩变大,也可以变化到其他位置转而书写别的内容。因此,模型接下来要输出的实际内容是不确定的,只能确定接下来要写什么的概率分布(probabilitydistribution )情况。我们需要从这个概率分布中取样,以此判定下一步要写的内容。

这两个假设可以用下图来概括,它描述了使用具有隐藏状态的RNN模型生成一个随机序列的过程。

艺术家如何应用RNN(循环神经网络)创作AI化的艺术作品

生成序列模型框架

如果你不能完全理解上图也不要担心。在下一节中,我们将用Javascript逐行演示该过程。

用于书写过程的循环神经网络

在前一节描述的书写任务中,我们已经预训练了一个循环神经网络模型。在本节中,我们将为大家展示如何利用p5.js在Javascript中使用这个模型。下面是整个书写产生过程的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来表示。为了从rnn_state获得pdf对象,我们在此运用get_pdf函数,比如:

pdf = Model.get_pdf(rnn_state);

额外变量temperature使得我们能够控制模型的确定或不确定性。结合pdf对象,我们可以在模型中使用sample 函数作为概率分布中下一组 (dx, dy, pen)值的样本。我们后续将使用以下函数:

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

现在,我们唯一需要的是控制书写颜色和跟踪浏览器屏幕尺寸的变量:

// stores thebrowser's dimensions

var screen_width = window.innerWidth;

var screen_height = window.innerHeight;

// color 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来初始化函数。

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);

}

设置好p5.js框架的 绘制函数(draw function)后,接下来就是书写生成的过程,绘制函数将以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 theabsolute 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) 变量值进行随机取样。如果笔先前曾经碰到过纸,那么它将会根据新的变量在屏幕上画一条线,并更新笔的全局定位。一旦笔的位置接近屏幕右侧时,就会重置书写过程,并重新开始。

综合以上所述,我们得到以下的书写草图。

艺术家如何应用RNN(循环神经网络)创作AI化的艺术作品

这样,你就能在web浏览器中进行书写了。其中,有几行代码是使用p5.js的Javascript写成的。

在不同温度下从概率分布中取样

变量pdf应当存储每个步骤完成后的下一笔画的概率分布。在后台,对象pdf实际上只包含复杂概率分布的众多参数(即正态分布的平均值和标准偏差)。这里我们选择dx和dy的概率分布,以此作为混合密度分布(Mixture DensityDistribution)的模型。

但混合密度分布究竟是什么呢?统计学家(数据科学家)喜欢用某些知名的、易于处理的数学模型来模拟概率分布,如正态分布(NormalDistribution)。他们努力确定最符合数据要求的分布参数(如正态分布的平均值和标准偏差)。然而,在处理诸如书写笔画等这样复杂的问题时,我们发现仅仅用正态分布模型并不能很好的拟合数据。从直观的角度来说,某处书写完成后,接下来要么会在原处附近继续书写,要么会跳转到其他位置重新书写。

解决这个问题的直接方法就是,将概率分布模拟为许多正态分布相互叠加的总和。

在我们给出的例子中,我们将书写笔画模拟为20个正态分布的总和。将这20个正态分布混合好之后,模型在实际书写方面的效果还算不错。如果你想了解更多的技术细节,可以查阅我们写的其他相关的文章。

当我们运用概率分布、从中取样并获得系列(dx, dy, pen)的值以确定下一步书写内容的时候,我们使用temperature参数来控制模型的不确定性。如果温度参数的值非常高,那么我们更有可能在概率分布的小概率区间上获得样本。如果温度参数的值非常低,或接近于零,那么我们只会从最有可能的分布区间获得样本。

在下面的图中,概率分布通过温度参数的变化而增强或减弱。

艺术家如何应用RNN(循环神经网络)创作AI化的艺术作品

为简单起见,以上的演示模拟的是20个一维正态分布在同一温度参数条件下的变化过程。而在书写模型中,模拟的是二十个概率分布的,二维正态分布。

艺术家如何应用RNN(循环神经网络)创作AI化的艺术作品

当保持低温时,书写模型非常稳定,因此笔迹更整洁、更逼真。提高温度会增加概率分布选择小概率选项的可能性,因此笔迹样本变得更加曲折、更加具有不确定性。

书写演示的拓展

将机器学习与设计相结合的一个更加有趣的方面就是,探索人类和机器之间的交互。典型的机器学习框架+python堆叠很难配置真正的交互式web应用程序,因为他们通常需要专门的web服务来编写服务器端,进而处理客户端的用户交互。Javascript框架的优点是交互式编程可以轻松完成,而且在web浏览器不用费大力气进行配置,如p5.js。

我们从基本的书写演示中可能建立的交互式扩展之一是让用户交互式地在屏幕上书写,而当用户空闲时,模型可以继续预测其余的书写样本。另一个可以建立的扩展,我们之前曾在distill.pub中提到过,与之类似。该拓展根据用户的书写路径习惯,创建多种路径可能性的模型样本。

除此之外,该模型还有无尽的其他可能性。把这个模型和更先进的框架如paper.js或d3.js相结合,以此生成更好看的笔风,也会十分有趣。

使用这个代码吧!

如果你是一个艺术家或设计师并且对机器学习感兴趣,你可以使用github资源库(其中包含这篇文章介绍的代码),并根据自己的喜好来运用它。

这篇文章只是介绍了循环神经网络RNNs的浅层知识。如果你想更多的参与到整个机器学习的开发过程中,并训练出自己的模型,网上有大量优秀的资源来帮助你学习如何利用TensorFlow或keras构建模型。

如果你使用keras来构建和训练模型,那么有一种叫做keras.js的工具,它会允许你输出用于web浏览器的预训练模型,所以你就可以构建如文中的Javascript书写模型一样的模型界面。我没有亲自用过keras.js,我只是喜欢在Javascript中从零开始编写书写模型。


编译: AI100

原文地址:http://blog.otoro.net/2017/01/01/recurrent-neural-network-artist


相关推荐