分享

Tensorflow实现RNN-LSTM的菜鸟指南

 昵称11935121 2018-10-14

什么是RNN?

简单的多层神经网络是分类器,当给定某个输入时,将输入标记为属于许多类之一。它们使用现有的反向传播算法进行训练。这些网络在他们所做的事情上表现出色,但他们无法处理按顺序排列的输入。例如,对于神经网络来识别句子中的名词,仅将单词作为输入是没有用的。在单词的上下文中存在许多信息,这些信息只能通过查看给定单词附近的单词来确定。要研究整个序列以确定输出。这是回归神经网络(RNN)发现它们的用途。当RNN遍历输入序列时,每个输入的输出也成为序列的下一项输入的一部分。Andrej Karpathy的精彩博文。注意网络的“循环”属性是有帮助的,其中输入项的先前输出变为当前输入的一部分,其包括序列中的当前项和最后一个输出。当反复完成时,最后一个输出将是所有先前输入和最后一个输入的结果。

什么是LSTM?

RNN非常适合于序列分类问题,它们之所以如此擅长,是因为它们能够保留先前输入的重要数据并使用该信息来修改当前输出。如果序列很长,那么在训练期间(反向传播)计算的梯度(计算用于调整网络的值)要么消失(多个0 <><>

长短期记忆是一种RNN架构,它解决了长序列训练和保留记忆的问题。LSTM通过引入一些控制对单元状态的访问的门来解决梯度问题。您可以参考Colah的博客文章,这是了解LSTM工作的好地方。如果你没有得到正在讨论的内容,那很好,你可以安全地转到下一部分。

任务

给定长度为20的二进制字符串(一个只有0和1的字符串),我们需要确定二进制字符串中的1的计数。例如,“01010010011011100110”有11个。因此,我们程序的输入将是一个长度为20的字符串,包含0和1,输出必须是0到20之间的单个数字,表示字符串中的1。以下是完整要点的链接,以防您只想跳转代码。

即使是业余程序员也不禁傻笑于任务定义。执行此程序并在每个输入上获得正确的输出(0%错误)不会超过一分钟。

input_string中的

i 为count = 0 :if i =='1':

count + = 1

任何心智正常的人都会想,如果这么容易,为什么计算机本身无法解决问题呢?没有人类教练,计算机就不那么聪明。需要给计算机提供精确的指令,并且“思考”必须由发出命令的人来完成。机器可以重复数十亿次最复杂的计算,但它们仍然在人类毫无痛苦地做的事情上失败,比如在图片中识别猫。

我们计划做的是为神经网络提供足够的输入数据,并告诉它输入的正确输出值。发布一下,我们会给它以前没见过的输入,我们会看到有多少程序是正确的。

生成训练输入数据

每个输入都是长度为二十的二进制字符串。我们将代表它的方式将是0和1的python列表。用于训练的测试输入将包含许多此类列表。

import numpy as np

from random import shuffle

train_input = ['{0:020b}'。format(i)for i in range(2 ** 20)]

shuffle(train_input)

train_input = [map(int,i)for i in train_input]

ti = []

for i in train_input:

temp_list = []

for j in i:

temp_list.append([j])

ti.append(np.array(temp_list))

train_input = ti

在长度为20的字符串中总共可以有220~106个1和0的组合。我们生成所有220个数字的列表,将其转换为二进制字符串并随机播放整个列表。然后将每个二进制字符串转换为0和1的列表。Tensorflow需要输入作为维度[batch_size,sequence_length,input_dimension](3d变量)的张量(Tensorflow变量)。在我们的例子中,batch_size是我们稍后会确定的,但sequence_length固定为20,input_dimension为1(即字符串的每个单独位)。每个位实际上都表示为仅包含该位的列表。20个这样的列表的列表将形成一个序列,我们将其转换为numpy数组。所有这些序列的列表是我们试图计算的train_input的值。如果打印train_input的前几个值,

[

array([[0],[0],[1],[0],[0],[1],[0],[1],[1],[0],[0],[0 ],[1],[1],[1],[1],[1],[1],[0],[0]]),

数组([[1],[1],[0] ,[0],[0],[0],[1],[1],[1],[1],[1],[0],[0],[1],[0],[ 0],[0],[1],[0],[1]]),

.....

]

如果它们与您的值不匹配,请不要担心这些值,因为它们会随机顺序不同。

生成训练输出数据

对于每个序列,结果可以是0到20之间的任何值。因此,每个序列有21个选择。很明显,我们的任务是序列分类问题。每个序列属于类号,该序列号与序列中的序列号相同。输出的表示将是长度为21的列表,除了序列所属类的索引处的一个零之外的所有位置都为零。

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

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

这是属于第4类的序列的示例输出,即有4个

更正式地说,这被称为一个热编码表示。

train_output = []

for i in train_input:

count = 0

for j in i:

if j [0] == 1:

count + = 1

temp_list =([0] * 21)

temp_list [count] = 1

train_output.append(temp_list)

对于每个训练输入序列,我们生成等效的一个热编码输出表示。

生成测试数据

对于任何受监督的机器学习任务,我们需要一些数据作为训练数据来教我们的程序识别正确的输出和一些数据作为测试数据,以检查我们的程序如何在输入上执行它以前没有见过的。让测试和训练数据重叠是弄巧成拙的,因为如果你已经练习了考试中的问题,那么你肯定会得到它。目前在我们的train_input和train_output中,我们有220个(1,048,576)个独特的例子。我们将这些分为两组,一组用于培训,另一组用于测试。我们将从数据集中获取10,000个示例(整个数据的0.9%)并将其用作训练数据,并使用1,038,576个示例中的其余部分作为测试数据。

NUM_EXAMPLES = 10000

test_input = train_input [NUM_EXAMPLES:]

test_output = train_output [NUM_EXAMPLES:] #everything超过10,000

train_input = train_input [:NUM_EXAMPLES]

train_output = train_output [:NUM_EXAMPLES] #till 10,000

设计模型

这是本教程中最重要的部分。Tensorflow和其他各种库(Theano,Torch,PyBrain)为用户提供了设计模型的工具,而没有深入了解实现神经网络,优化或反向传播算法的细节。

Danijar概述了组织Tensorflow模型的好方法,您可能希望稍后使用它来整理代码。出于本教程的目的,我们将跳过这一点,并专注于编写正常工作的代码。

首先导入所需的包。如果您尚未安装Tensorflow,请按照此页面上的说明进行操作,然后继续。

导入张量流为tf

导入张量流后,我们将定义两个变量来保存输入数据和目标数据。

data = tf.placeholder(tf.float32,[None,20,1])

target = tf.placeholder(tf.float32,[None,21])

数据的尺寸为[批量大小,序列长度,输入尺寸]。我们让批量大小未知,并在运行时确定。目标将保存训练输出数据,这是我们想要的正确结果。我们已经制作了Tensorflow占位符,它们基本上就是它们,占位符将在稍后提供数据。

现在我们将创建RNN单元。Tensorflow支持LSTM,GRU(与LSTM略有不同的架构)和简单的RNN单元。我们将使用LSTM完成此任务。

num_hidden = 24

cell = tf.nn.rnn_cell.LSTMCell(num_hidden,state_is_tuple = True)

对于我们初始化的每个LSTM单元,我们需要为隐藏维度提供值,或者像某些人喜欢称之为LSTM单元格中的单元数量。它的价值取决于您,过高的值可能导致过度拟合,或者非常低的值可能会产生极差的结果。正如许多专家所说,选择正确的参数更像是一门艺术而非科学。

在我们编写更多代码之前,必须了解Tensorflow计算图如何工作。从黑客的角度来看,将其视为分为两个阶段就足够了。在第一个阶段是建立在其中定义所有的计算和函数计算图,你会在运行时执行。的第二阶段是在其中创建一个Tensorflow会话中的执行阶段和这是先前定义被执行的图形与我们提供的数据。

val,state = tf.nn.dynamic_rnn(cell,data,dtype = tf.float32)

我们展开网络并将数据传递给它并将输出存储在val中。我们还将动态运行结束时的状态作为返回值,但我们将其丢弃,因为每次查看新序列时,状态对我们都无关紧要。请注意,编写这行代码并不意味着它已被执行。我们仍处于设计模型的第一阶段。将这些视为存储在变量中的函数,这些变量将在我们启动会话时调用。

val = tf.transpose(val,[1,0,2])

last = tf.gather(val,int(val.get_shape()[0]) - 1)

我们将输出转换为切换批量大小的序列大小。之后我们只在序列的最后一个输入中获取输出值,这意味着在20的字符串中我们只对我们在第20个字符处获得的输出感兴趣,而在此字符的输出的其余部分是无关紧要的。

weight = tf.Variable(tf.truncated_normal([num_hidden,int(target.get_shape()[1])]))

bias = tf.Variable(tf.constant(0.1,shape = [target.get_shape()[1] ]))

我们想要做的是将最终转换应用于LSTM的输出并将其映射到21个输出类。我们定义权重和偏差,并将输出与权重相乘,并为其添加偏差值。权重的维度将是num_hidden X number_of_classes。因此,在与输出(val)相乘时,得到的维度将是batch_size X number_of_classes,这正是我们正在寻找的。

预测= tf.nn.softmax(tf.matmul(最后,体重)+偏见)

在将输出与权重相乘并添加偏差之后,我们将为每个类提供一个具有各种不同值的矩阵。我们感兴趣的是每个类的概率分数,即序列属于特定类的机会。然后我们计算softmax激活以给出概率分数。

这个功能是什么?我们为什么要使用它?

Tensorflow实现RNN-LSTM的菜鸟指南

此函数接受值向量,并根据其值返回每个索引的概率分布。此函数返回概率分数(所有值的总和等于1),这是我们需要的最终输出。如果您想了解有关softmax的更多信息,请访问此链接。

cross_entropy = -tf.reduce_sum(target * tf.log(tf.clip_by_value(prediction,1e-10,1.0)))

下一步是计算损失或用较少的技术词汇来衡量我们的不正确程度。我们计算交叉熵损失(这里有更多细节)并将其用作我们的成本函数。成本函数将帮助我们确定我们的预测与实际结果的叠加程度有多差或有多好。这是我们试图最小化的功能。如果您不想深入研究技术细节,可以直接了解交叉熵损失的计算方法。日志术语有助于我们衡量网络对错的程度。比方说,如果目标是1并且预测接近1,我们的损失就不会太大,因为-log(x)的值,其中x接近1几乎为0.对于同一目标,如果预测为0 ,当x接近于零时,-log(x)非常高,因此成本会增加很多。添加日志项有助于在模型非常错误时更多地惩罚模型,而在预测接近目标时则很少。

optimizer = tf.train.AdamOptimizer()

minimize = optimizer.minimize(cross_entropy)

Tensorflow有一些优化函数,如RMSPropOptimizer,AdaGradOptimizer等。我们选择AdamOptimzer并将最小化设置为最小化我们先前计算的交叉熵损失的函数。

计算测试数据的错误

mistakes = tf.not_equal(tf.argmax(target,1),tf.argmax(prediction,1))

error = tf.reduce_mean(tf.cast(errors,tf.float32))

此错误是测试数据集中有多少序列被错误分类的计数。这使我们了解模型在测试数据集上的正确性。

执行图表

我们完成了模型的设计。现在模型将被执行!

init_op = tf.initialize_all_variables()

sess = tf.Session()

sess.run(init_op)

我们启动一个会话并初始化我们定义的所有变量。之后,我们开始培训过程。

batch_size = 1000

no_of_batches = int(len(train_input)/ batch_size)

epoch = 5000

for i in range(epoch):

ptr = 0

表示j在范围内(no_of_batches):inp

,out = train_input [ptr:ptr + batch_size],train_output [ptr:ptr + batch_size]

ptr + = batch_size

sess.run(minimize,{data:inp,target:out})

print“Epoch - ”,str(i)

incorrect = sess.run(error,{data:test_input,target :test_output})

print('Epoch {:2d}错误{:3.1f}%'。format(i + 1,100 *不正确))

sess.close()

我们决定批量大小并相应地划分训练数据。我已将批量大小修正为1000,但您希望通过更改它来进行实验,以了解它如何影响您的结果和培训时间。

如果你熟悉随机梯度下降,这个想法似乎相当简单。我们不是在运行所有训练样本后更新值,而是将训练集分成更小的批次并为其运行。处理完每批次后,将调整网络的值。因此,每隔几步,就会调整网络权重。已知随机优化方法对于某些功能比其对应方更好地执行。这是因为随机方法收敛得快得多,但情况并非总是如此。

对于每个批次,我们获取输入和输出数据,并且我们运行最小化,优化器功能以最小化成本。所有预测,成本和反向传播的计算都是通过张量流来完成的。我们将sess.run中的feed_dict与函数一起传递。feed_dict是一种将数据分配给该帧中的tensorflow变量的方法。因此,我们将输入数据与目标(正确)输出一起传递。我们上面写的函数现在正在执行。

就这样。我们已经制作了我们的玩具LSTM-RNN,只需查看正确的例子即可学会计算!当我第一次训练它时,这对我来说不是很直观,所以我在错误计算下面添加了这行代码,它将打印特定示例的结果。

print sess.run(model.prediction,{data:[[[1],[0],[0],[1],[1],[0],[1],[1],[1], [0],[1],[0],[0],[1],[1],[0],[1],[1],[1],[0]]]})

因此,当模型训练时,您将注意到列表中正确索引处的概率分数如何逐渐增加。这是代码完整要点的链接。

对培训数据的担忧

许多人会问,为什么要使用仅占所有数据1%的训练数据集。好吧,为了能够在具有单核的CPU上训练它,更高的数字会以指数方式增加时间。您当然可以调整批量大小以保持更新的数量相同,但最终的决定始终取决于模型设计者。尽管如此,当您意识到1%的数据足以让网络获得出色的结果时,您会对结果感到惊讶!

修补模型

您可以尝试更改参数值,以了解它如何影响性能和培训时间。您还可以尝试向RNN添加多个图层,以使您的模型更加复杂,并使其能够学习更多功能。您可以实现的一个重要功能是添加在每几次迭代后保存模型值的功能,并检索这些值以便将来执行预测。您还可以将单元格从LSTM更改为GRU或简单的RNN单元格并比较性能。

结果

使用10,000个序列训练模型,在MacbookPro / 8GB / 2.4Ghz / i5上批量大小为1,000和5000个时期,没有GPU花了我大约3-4个小时。现在回答这个问题,每个人都在等待。它的表现如何?

Epoch 5000错误0.1%

对于最后一个时期,整个错误率为0.1%(几乎是因为我们的测试数据是所有可能组合的99%)数据集!这非常接近具有最少编程技能的人能够实现的(0%错误)。但是,我们的神经网络自己想出来了!我们没有指示它执行任何计数操作。

如果要加快进程,可以尝试减少二进制字符串的长度,并调整代码中其他位置的值以使其工作。

你现在可以做什么?

既然您已经实施了LSTM模型,那么还有什么可以做的?序列分类可以应用于许多不同的问题,如手写数字识别甚至自动驾驶汽车!将图像的行视为单独的步骤或输入,将整个图像视为序列。您必须将图像归类为属于可以停止,加速,左转,右转或以相同速度继续的类别之一。训练数据可能是一个阻碍但是地狱,你甚至可以自己生成它。还有很多等待的事情要做!

Tensorflow实现RNN-LSTM的菜鸟指南

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多