Keras结合Keras后端搭建个性化神经网络模型(不用原生Tensorflow)

  Keras是基于Tensorflow等底层张量处理库的高级API库。它帮咱们实现了一系列经典的神经网络层(全链接层、卷积层、循环层等),以及简洁的迭代模型的接口,让咱们能在模型层面写代码,从而不用仔细考虑模型各层张量之间的数据流动。后端

  可是,当咱们有了全新的想法,想要个性化模型层的实现,Keras的高级API是不能知足这一要求的,而换成Tensorflow又要从新写不少轮子,这时,Keras的后端就派上用场了。Keras将底层张量库的函数功能统一封装在“backend”中,用户能够用统一的函数接口调用不一样的后端实现的相同功能。因此,若是不追求速度的话,能够仅使用Keras实现你的任何独特想法,从而避免使用原生Tensorflow写重复的轮子。网络

  咱们定义并训练一个神经网络模型须要考虑的要素有三个:层、损失函数、优化器。而咱们创新主要在于前两个,所以下面介绍如何结合Keras高级API与后端,自定义特殊神经网络层以及损失函数。dom

自定义网络层

  自定义层能够经过两种方式实现:使用Lambda层和继承Layer类。函数

lambda层

  Lambda层仅能对输入作固定的变换,并不能定义能够经过反向传播训练的参数(经过Keras的fit训练),所以能实现的东西较少。如下代码实现了Dropout的功能:优化

from keras import backend as K
from keras import layers

def my_layer(x):     
  mask = K.random_binomial(K.shape(x),0.5)
  return x*mask*2
x = layers.Lambda(my_layer)(x) 

  其中my_layer函数是自定义层要实现的操做,传递参数只能是Lambda层的输入。定义好函数后,直接在layers.Lambda中传入函数对象便可。实际上,这些变换不整合在lambda层中而直接写在外面也是能够的:ui

from keras import backend as K
from keras import layers

x = layers.Dense(500,activation='relu')(x) 
mask = K.random_binomial(K.shape(x),0.5)
x = x*mask*2

  数据先通过一个全链接层,而后再被0.5几率Dropout。以上实现Dropout只是做举例,你能够以一样的方式实现其它的功能。spa

继承layer类

  若是你想自定义能够训练参数的层,就须要继承实现Keras的抽象类Layer。主要实现如下三个方法:.net

  一、__init__(self, *args, **kwargs):构造函数,在实例化层时调用。此时尚未添加输入,也就是说此时输入规模未知,但能够定义输出规模等与输入无关的变量。类比于Dense层里的units、activations参数。code

  二、build(self, input_shape):在添加输入时调用(__init__以后),且参数只能传入输入规模input_shape。此时输入规模与输出规模都已知,能够定义训练参数,好比全链接层的权重w和偏执b。orm

  三、call(self, *args, **kwargs):编写层的功能逻辑。

单一输入

  当输入张量只有一个时,下面是实现全链接层的例子:

import numpy as np
from keras import layers,Model,Input,utils
from keras import backend as K
import tensorflow as tf

class MyDense(layers.Layer): 
  def __init__(self, units=32): #初始化
    super(MyDense, self).__init__()#初始化父类
    self.units = units  #定义输出规模
  def build(self, input_shape):   #定义训练参数
    self.w = K.variable(K.random_normal(shape=[input_shape[-1],self.units]))  #训练参数
    self.b = tf.Variable(K.random_normal(shape=[self.units]),trainable=True)  #训练参数
    self.a = tf.Variable(K.random_normal(shape=[self.units]),trainable=False) #非训练参数
  def call(self, inputs): #功能实现
    return K.dot(inputs, self.w) + self.b
  
#定义模型
input_feature = Input([None,28,28]) 
x = layers.Reshape(target_shape=[28*28])(input_feature)
x = layers.Dense(500,activation='relu')(x)  
x = MyDense(100)(x)
x = layers.Dense(10,activation='softmax')(x) 
  
model = Model(input_feature,x) 
model.summary() 
utils.plot_model(model)

  模型结构以下:

  在build()中,训练参数能够用K.variable或tf.Variable定义。而且,只要是用这两个函数定义并存入self中,就会被keras认定为训练参数,不论是在build仍是__init__或是其它函数中定义。可是K.variable没有trainable参数,不能设置为Non-trainable params,因此仍是用tf.Variable更好更灵活些。

多源输入

  若是输入包括多个张量,须要传入张量列表。实现代码以下:

import numpy as np
from keras import layers,Model,Input,utils
from keras import backend as K
import tensorflow as tf

class MyLayer(layers.Layer): 
  def __init__(self, output_dims):
    super(MyLayer, self).__init__()  
    self.output_dims = output_dims
  def build(self, input_shape):  
    [dim1,dim2] = self.output_dims
    self.w1 = tf.Variable(K.random_uniform(shape=[input_shape[0][-1],dim1]))
    self.b1 = tf.Variable(K.random_uniform(shape=[dim1]))  
    self.w2 = tf.Variable(K.random_uniform(shape=[input_shape[1][-1],dim2])) 
    self.b2 = tf.Variable(K.random_uniform(shape=[dim2])) 
  def call(self, x): 
    [x1, x2] = x
    y1 = K.dot(x1, self.w1)+self.b1 
    y2 = K.dot(x2, self.w2)+self.b2
    return K.concatenate([y1,y2],axis = -1)
 
#定义模型
input_feature = Input([None,28,28])#输入
x = layers.Reshape(target_shape=[28*28])(input_feature) 
x1 = layers.Dense(500,activation='relu')(x)  
x2 = layers.Dense(500,activation='relu')(x)  
x = MyLayer([100,80])([x1,x2])   
x = layers.Dense(10,activation='softmax')(x) 
  
model = Model(input_feature,x) 
model.summary() 
utils.plot_model(model,show_layer_names=False,show_shapes=True)

  模型结构以下:

  总之,传入张量列表,build传入的input_shape就是各个张量形状的列表。其它都与单一输入相似。

自定义损失函数

  根据Keras能添加自定义损失的特性,这里将添加损失的方法分为两类:

  一、损失须要根据模型输出与真实标签来计算,也就是只有模型的输出与外部真实标签做为计算损失的参数。

  二、损失无需使用外部真实标签,也就是只用模型内部各层的输出做为计算损失的参数。

  这两类损失添加的方式并不同,但愿之后Keras能把API再改善一下,这种冗余有时让人摸不着头脑。

第一类损失

  这类损失能够经过自定义函数的形式来实现。函数的参数必须是两个:真实标签与模型输出,不能多也不能少,而且顺序不能变。而后你能够在这个函数中定义你想要的关于输出与真实标签之间的损失。而后在model.compile()中将这个函数对象传给loss参数。代码示例以下(参考连接):

def customed_loss(true_label,predict_label): 
  loss = keras.losses.categorical_crossentropy(true_label,predict_label) loss += K.max(predict_label) return loss model.compile(optimizer='rmsprop', loss=customed_loss)

  若是硬是想用这种方法把模型隐层的输出拿来算损失的话,也不是不能够。只要把相应隐层的输出添加到模型的输出列表中,自定义损失函数就能够从模型输出列表中取出隐层输出来用了。即:

model = Model(input,[model_output, hidden_layer_output])

  固然,这样就把模型结构改了,若是不想改模型的结构而添加“正则化”损失,可使用下面的方法。

第二类损失

  这类损失能够用Model.add_loss(loss)方法实现,loss可使用Keras后端定义计算图来实现。可是显然,计算图并不能把将来训练用的真实标签传入,因此,add_loss方法只能计算模型内部的“正则化”损失。

  add_loss方法可使用屡次,损失就是屡次添加的loss之和。使用了add_loss方法后,compile中就能够不用给loss赋值,不给loss赋值的话使用fit()时就不能传入数据的标签,也就是y_train。若是给compile的loss赋值,最终的目标损失就是屡次add_loss添加的loss和compile中loss之和。另外,若是要给各项损失加权重的话,直接在定义loss的时候加上便可。代码示例以下:

loss = 100000*K.mean(K.square(somelayer_output))#somelayer_output是定义model时得到的某层输出
model.add_loss(loss)
model.compile(optimizer='rmsprop')

  以上讲的都是关于层输出的损失,层权重的正则化损失并不这样添加,自定义正则项能够看下面。

  keras中添加正则化_Bebr的博客-CSDN博客_keras 正则化

  里面介绍了已实现层的自定义正则化,但没有介绍自定义层的自定义正则化,这里先挖个坑,之后要用再研究。

相关文章
相关标签/搜索