【啄米日常】3:一个不负责任的Keras介绍(中)

【啄米日常】3:一个不负责任的Keras介绍(中)

上回书我们简单的把Keras的框架理了一下,下面我们深入(也不怎么深)具体的模块理一下Keras,主要聊一聊每个模块的具体功能和核心函数

backend:百货商店

backend这个模块的主要作用,是对tensorflow和theano的底层张量运算进行了包装。用户不用关心具体执行张量运算的是theano还是tensorflow,就可以编写出能在两个框架下可以无缝对接的程序。backend中的函数要比文档里给出的多得多,完全就是一家百货商店。但一般情况下,文档里给出的那些就已经足够你完成大部分工作了,事实上就连文档里给出的函数大部分情况也不会用,这里提几个比较有用的函数——当然是对我来说比较有用,毕竟这是一份不怎么负责任的介绍,如果你想找对你有用的函数,就去backend淘一淘吧~:

  • function:毫无疑问这估计是最有用的一个函数了,function用于将一个计算图(计算关系)编译为具体的函数。典型的使用场景是输出网络的中间层结果。
  • image_ordering和set_image_ordering:这组函数用于返回/设置图片的维度顺序,由于Theano和Tensorflow的图片维度顺序不一样,所以有时候需要获取/指定。典型应用是当希望网络自适应的根据使用的后端调整图片维度顺序时。
  • learning_phase:这个函数的主要作用是返回网络的运行状态,0代表测试,1代表训练。当你需要便携一个在训练和测试是行为不同的层(如Dropout)时,它会很有用。
  • int_shape:这是我最常用的一个函数,用于以整数tuple的形式返回张量的shape。要知道从前网络输出张量的shape是看都看不到的,int_shape可以在debug时起到很大作用。
  • gradients: 求损失函数关于变量的导数,也就是网络的反向计算过程。这个函数在不训练网络而只想用梯度做一点奇怪的事情的时候会很有用,如图像风格转移。

backend的其他大部分函数的函数名是望而知义的,什么max,min,equal,eval,zeros,ones,conv2d等等。函数的命名方式跟numpy差不多,下次想用时不妨先‘.’一下,说不定就有。

models/layers:Keras的核心主题

使用Keras最常见的目的,当然还是训练一个网络。之前说了网络就是张量到张量的映射,所以Keras的网络,其实是一个由多个子计算图构成的大计算图。当这些子计算图是顺序连接时,称为Sequential,否则就是一般的model,我们称为泛型模型。

模型不但是张量的计算方式,还是层对象的容器,模型用来将它所含有的层整合起来,大家手拉手一起走

模型有两套训练和测试的函数,一套是fit,evaluate等,另一套是fit_generator,evaluate_generator,前者适用于普通情况,后者适用于数据是以迭代器动态生成的情况。迭代器可以在内存/显存不足,实时动态数据提升进行网络训练,所以使用Keras的话,Python的迭代器这一部分是一定要掌握的内容。对模型而言,最核心的函数有两个:

  • compile():编译,模型在训练前必须编译,这个函数用于完成添加正则项啊,确定目标函数啊,确定优化器啊等等一系列模型配置功能。这个函数必须指定的参数是优化器和目标函数,经常还需要指定一个metrics来评价模型。

  • fit()/fit_generator():用来训练模型,参数较多,是需要重点掌握的函数,对于keras使用者而言,这个函数的每一个参数都需要掌握。

其他的函数请自己学习。

另外,模型还有几个常用的属性和函数:

  • layers:该属性是模型全部层对象的列表,是的就是一个普通的python list
  • get_layer():这个函数通过名字来返回模型中某个层对象
  • pop():这个函数文档里没有,但是可以用。作用是弹出模型的最后一层,从前进行finetune时没有pop,大家一般用model.layers.pop()来完成同样的功能。

当然,因为Model是Layer的子类,Layer的所有属性和方法也自动被Model所有,这些有用的属性稍后介绍。

Keras的层对象是构筑模型的基石,除了卷积层,递归神经网络层,全连接层,激活层这种烂大街的Layer对象外,Keras还有一些不是那么烂大街的东西:

  • Advanced Activation:高级激活层,主要收录了包括leakyReLU,pReLU,ELU,SReLU等一系列高级激活函数,这些激活函数不是简单的element-wise计算,所以单独拿出来实现一下
  • Merge层:这个层用于将多个层对象的输出组合起来,支持级联、乘法、余弦等多种计算方式,它还有个小兄弟叫merge,这个函数完成与Merge相同的作用,但输入的对象是张量而不是层对象。
  • Lambda层:这是一个神奇的层,看名字就知道它用来把一个函数作用在输入张量上。这个层可以大大减少你的工作量,当你需要定义的新层的计算不是那么复杂的时候,可以通过lambda层来实现,而不用自己完全重写。
  • Highway/Maxout/AtrousConvolution2D层:这个就不多说了,懂的人自然懂,keras还是在一直跟着潮流走的
  • Wrapper层:Wrapper层用于将一个普通的层对象进行包装升级,赋予其更多功能。目前,Wrapper层里有一个TimeDistributed层,用于将普通的层包装为对时间序列输入处理的层,而Bidirectional可以将输入的递归神经网络层包装为双向的(如把LSTM做成BLSTM)
  • Input:补一个特殊的层,Input,这个东西实际上是一个Keras tensor的占位符,主要用于在搭建Model模型时作为输入tensor使用,这个Input可以通过keras.layers来import。
  • stateful与unroll:Keras的递归神经网络层,如SimpleRNN,LSTM等,支持两种特殊的操作。一种是stateful,设置stateful为True意味着训练时每个batch的状态都会被重用于初始化下一个batch的初始状态。另一种是unroll,unroll可以将递归神经网络展开,以空间换取运行时间。

Keras的层对象还有一些有用的属性和方法,比较有用的是:

  • name:别小看这个,从茫茫层海中搜索一个特定的层,如果你对数数没什么信心,最好是name配合get_layer()来用。
  • trainable:这个参数确定了层是可训练的还是不可训练的,在迁移学习中我们经常需要把某些层冻结起来而finetune别的层,冻结这个动作就是通过设置trainable来实现的。
  • input/output:这两个属性是层的输入和输出张量,是Keras tensor的对象,这两个属性在你需要获取中间层输入输出时非常有用
  • get_weights/set_weights:这是两个方法用于手动取出和载入层的参数,set_weights传入的权重必须与get_weights返回的权重具有同样的shape,一般可以用get_weights来看权重shape,用set_weights来载入权重

既然是核心主题,我们就多唠两句,在Keras中经常有的一个需求是需要自己编写一个新的层,如果你的计算比较简单,那可以尝试通过Lambda层来解决,如果你不得不编写一个自己的层,那也不是什么大不了的事儿。前两天群里有朋友想编写一个卷积核大小不一样的卷积层(虽然不知道为啥他这么想不开……活着不好吗?),这个显然就要自己编写层了。

要在Keras中编写一个自己的层,需要开一个从Layer(或其他层)继承的类,除了__init__以为你需要覆盖三个函数:

  • build,这个函数用来确立这个层都有哪些参数,哪些参数是可训练的哪些参数是不可训练的。
  • call,这个函数在调用层对象时自动使用,里面就是该层的计算逻辑,或计算图了。显然,这个层的核心应该是一段符号式的输入张量到输出张量的计算过程。
  • get_output_shape_for:如果你的层计算后,输入张量和输出张量的shape不一致,那么你需要把这个函数也重新写一下,返回输出张量的shape,以保证Keras可以进行shape的自动推断

其实也不难~是吧,不要忘记Keras是基于Python的框架,你可以随时随地查看那些已经写好的层的代码,模仿着看看你自己的层要怎么写~

优化器,目标函数,初始化策略,等等...

和model,layers这种核心功能相比,这些模块的重要性就没有那么大,我们简单介绍一下,里面的具体技术,(下)篇可能会说,也可能不会……我还没想好,但是基本上不说也没什么影响

objectives是优化目标, 它本质上是一个从张量到数值的函数,当然,是用符号式编程表达的。具体的优化目标有mse,mae,交叉熵等等等等,根据具体任务取用即可,当然,也支持自己编写。需要特别说明的一点是,如果选用categorical_crossentropy作为目标函数,需要将标签转换为one-hot编码的形式,这个动作通过utils.np_utils.to_categorical来完成(记得上篇我就提过了)

optimizers是优化器,没什么可说了,如何选用合适的优化器不在本文讨论范畴。注意模型是可以传入优化器对象的,你可以自己配置一个SGD,然后将它传入模型中。 另外,最新版本的Keras为所有优化器额外设置了两个参数clipnorm和clipvalue,用来对梯度进行裁剪。

activation是激活函数,这部分的内容一般不直接使用,而是通过激活层Activation来调用,此处的激活函数是普通的element-wise激活函数,如果想使用高级激活函数,请翻到高级激活函数层。

callback是回调函数,这其实是一个比较重要的模块,回调函数不是一个函数而是一个类,用于在训练过程中收集信息或进行某种动作。比如我们经常想画一下每个epoch的训练误差和测试误差,那这些信息就需要在回调函数中收集。预定义的回调函数中CheckModelpoint,History和EarlyStopping都是比较重要和常用的。其中CheckPoint用于保存模型,History记录了训练和测试的信息,EarlyStopping用于在已经收敛时提前结束训练。回调函数LearningRateScheduler支持按照用户的策略调整学习率,做模型精调或研究优化器的同学可能对这个感兴趣。

值得注意的是,History是模型训练函数fit的返回值,也就是说即使你没有使用任何回调函数,找一个变量接住model.fit(),还是能得到不少训练过程中的有用信息。

另外,回调函数还支持将信息发送到远程服务器,以及与Tensorflow的tensorboard联动,在网页上动态展示出训练和测试的情况(需要使用tensorflow为后端)

回调函数支持用户自定义,定义方法也非常简单,请参考文档说明编写即可

初始化方法,正则项,约束项,可视化没有什么特别值得注意的,就略过了。Keras中所有的模块都是可以用户自己定义的,这就是开源和Python的魅力,讲真你让我拿C++写这么个东西……我光把结构摸清楚就要吐血了!

另一个文档中没有但实际上有的东西是metrices,这里面定义了一系列用于评价模型的指标,例如“accuracy”。在训练模型时,可以选择一个或多个指标来衡量模型性能。

数据预处理和utils

数据预处理是Keras提供的用于预处理图像、文本和序列数据的一套工具,这个地方就属于各回各家各找各妈了,你处理什么问题就从这里面找什么工具。

特别指出的是,数据预处理的图像预处理部分,提供了一套用于实时图像数据提升的工具,这个东西支持用各种各样的方法对输入图像进行数据提升,然后以生成器的形式返回。另外,该工具还支持从文件夹中自动生成数据和标签,简直方便的不要不要的。

utils文档中没有,里面包含的函数也不必怎么了解,除了两个。一个是说了很多遍的to_catgoraical,另一个是convert_kernel。后者的主要作用是把卷积滤波器的卷积核在th和tf之间互相转换。theano和tensorflow相爱想杀,到处搞对抗。其中之一就是卷积核,卷积这个东西,按照信号与系统(哼,才不会告诉你们我是信号系统助教咧)的定义,是翻转->平移->相乘->相加。但反正卷积网络的卷积核都是训练出来的,翻转不翻转有什么关系?

所以有些人没翻转,有些人翻转了。是的,说的就是你俩,theano和tensorflow。于是如果一个网络预训练权重是由其中一种后端训练出来的,又要在另一种后端上跑,那么你就需要用kernel_convert这个函数搞一搞了。

估计这事儿太不地道,作者也看不下去了。现在utils出了一个新的layer_utils,里面有一个convert_all_kernels_in_model函数,用来把一个模型的所有卷积核全部进行转换,以后就用这个吧~

与scikit-learn联动

上一篇有人留言说希望多讲点这块的内容,很抱歉……我……我也不怎么会,原谅我毕竟是一只菜鸡

虽然不怎么会,但是不妨碍我知道这应该是一个非常重要和有潜力的功能。Keras与scikit-learn的协作通过keras.wrapper实现,在这个脚本里定义了两个类,分别是KerasClassifier和KerasRegressor,搭建好Sequential模型(只能是Sequential)将被它们包装为sklearn的分类器和迭代器加入的sklearn的工作流中。

这里有一个使用sklearn对Keras进行超参数调整的例子,大家可以参考这篇文章学习Keras和sklearn的联动:Keras/Python深度学习中的网格搜索超参数调优(附源码)

中篇就到这里,下篇来介绍Keras的实现原理/原则,常见问题与解答,以及Keras中比较隐蔽和诡异的坑。可能会过一段时间才发哟~最近还是略忙

另外你们觉得我这种菜鸡能找到啥工作啊有没有内推什么的求往脸上砸!

编辑于 2017-03-23 15:13