前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >四个用于Keras的很棒的操作(含代码)

四个用于Keras的很棒的操作(含代码)

作者头像
AiTechYun
发布2018-12-13 11:05:37
3K0
发布2018-12-13 11:05:37
举报
文章被收录于专栏:ATYUN订阅号ATYUN订阅号

编译:yxy

出品:ATYUN订阅号

Keras是最广泛使用的深度学习框架之一。它在易于使用的同时,在性能方面也与TensorFlow,Caffe和MXNet等更复杂的库相当。除非你的应用程序需要一些非常低级别和复杂的代码,否则Keras会为你提供最好的帮助!

而对于Keras来说,还有更多的东西可以满足你的需求。今天我们分享了一些相对少用但又很棒的东西,你可以用Keras和你需要的代码来实现它。这些将帮助你直接在Keras中编写所有自定义内容,而无需切换到其他更繁琐和复杂的库。

自定义度量和损失函数

Keras自带许多内置度量和损失函数,这些函数在大多数情况下都非常有用。但很可惜,只有最常见的度量和损失函数是内置的。所有度量基本都是某种形式的准确率,损失倒是有很多选择,但最新的研究成果并不多。如果你想要一些前沿的东西,你需要自己实现。

而这就是我们要做的了!所有Keras损失和度量的定义方式与具有两个输入变量的函数相同:地面真值(ground truth)和预测值,函数始终返回度量或损失的值。你唯一需要注意的是,矩阵上的任何操作都应该Keras与TensorFlow的Tensors完全兼容,因为这是Keras总是期望从这些自定义函数中获得的格式。这可以通过使用Python的math,Keras或TensorFlow操作来实现。

看起来很简单!以下是如何创建和应用自定义损失和自定义度量的示例。我实现了通常用于度量图像质量的PSNR度量。而对于损失函数,我实现了Charbonnier,它已经被证明比L1或L2损失更能抵抗异常值。我们编写函数后,只需将它们传递给我们的模型编译函数即可!

Charbonnier:https://arxiv.org/pdf/1701.03077.pdf

代码语言:javascript
复制
import math
代码语言:javascript
复制
from kerasimport backend as K
代码语言:javascript
复制
代码语言:javascript
复制
# Define our custom metric
代码语言:javascript
复制
def PSNR(y_true, y_pred):
代码语言:javascript
复制
    max_pixel= 1.0
代码语言:javascript
复制
    return 10.0 * math.log10((max_pixel** 2)/ (K.mean(K.square(y_pred- y_true))))
代码语言:javascript
复制
代码语言:javascript
复制
# Define our custom loss function
代码语言:javascript
复制
def charbonnier(y_true, y_pred):
代码语言:javascript
复制
    epsilon= 1e-3
代码语言:javascript
复制
    error= y_true- y_pred
代码语言:javascript
复制
    p= K.sqrt(K.square(error)+ K.square(epsilon))
代码语言:javascript
复制
    return K.mean(p)
代码语言:javascript
复制
代码语言:javascript
复制
# Compile our model
代码语言:javascript
复制
adam= Adam(lr=0.0001)
代码语言:javascript
复制
model.compile(loss=[charbonnier], metrics=[PSNR], optimizer=adam)

自定义层

与度量和损失函数类似,如果你想要使用标准卷积,池化和激活函数之外的东西,你可能会发现自己需要创建自定义的层。在这种情况下,你可以按照我在下面给出的代码示例来实现它!

从Keras文档中我们最需要实现的是:

  • call(x):这就是层的逻辑所在。除非你希望你的层支持屏蔽(mask),否则你只需关心传递给call的第一个参数:输入张量。
  • get_output_shape_for(input_shape):如果你的层修改了其输入的形状,则应在此处指定形状转换的逻辑。这可以让Keras进行自动形状推断。

在下面的例子中,我想要一个能自动将图片调整到我想要的大小的层。为此,我需要使用blinear,bicubic或最近邻调整(nearest neighbour resizing)。我定义了call()函数的第一个输入为x(即图像张量),和第二个输入(可选)method(这是我要选择的调整大小的方法。调整的scale被定义在初始化函数__init__内 。要坚持使用TensorFlow操作(所以我们总是使用Keras或TensorFlow张量),我们根据取整的scale调整并返回图像。在get_output_shape_for()函数中我计算并返回输出张量的完整形状。

现在我们已经编写了自定义层的代码,假设我们的图像张量被定义为image,我们要将它与Functional API一起使用,就像这样调用它:

image_2 = resize_layer(scale = 2)(image,method =“bilinear”)

代码语言:javascript
复制
import tensorflow as tf
代码语言:javascript
复制
代码语言:javascript
复制
def tf_int_round(num):
代码语言:javascript
复制
    return tf.cast(tf.round(num), dtype=tf.int32)
代码语言:javascript
复制
代码语言:javascript
复制
class resize_layer(layers.Layer):
代码语言:javascript
复制
    # Initialize variables
代码语言:javascript
复制
    def __init__(self, scale,**kwargs):
代码语言:javascript
复制
        self.scale= scale
代码语言:javascript
复制
        super(resize_layer,self).__init__(**kwargs)
代码语言:javascript
复制
代码语言:javascript
复制
    def build(self, input_shape):
代码语言:javascript
复制
        super(resize_layer,self).build(input_shape)
代码语言:javascript
复制
代码语言:javascript
复制
    # Defining how we will call our function   
代码语言:javascript
复制
    def call(self, x, method="bicubic"):
代码语言:javascript
复制
        height= tf_int_round(tf.cast(tf.shape(x)[1],dtype=tf.float32)* self.scale)
代码语言:javascript
复制
        width= tf_int_round(tf.cast(tf.shape(x)[2],dtype=tf.float32)* self.scale)
代码语言:javascript
复制
代码语言:javascript
复制
        if method== "bilinear":
代码语言:javascript
复制
            return tf.image.resize_bilinear(x, size=(height, width))
代码语言:javascript
复制
        elif method== "bicubic":
代码语言:javascript
复制
            return tf.image.resize_bicubic(x, size=(height, width))
代码语言:javascript
复制
        elif method== "nearest":
代码语言:javascript
复制
            return tf.image.resize_nearest_neighbor(x, size=(height, width))
代码语言:javascript
复制
代码语言:javascript
复制
    # Defining the computation of the output shape
代码语言:javascript
复制
    def get_output_shape_for(self, input_shape):
代码语言:javascript
复制
        height= tf_int_round(tf.cast(tf.shape(x)[1],dtype=tf.float32)* self.scale)
代码语言:javascript
复制
        width= tf_int_round(tf.cast(tf.shape(x)[2],dtype=tf.float32)* self.scale)
代码语言:javascript
复制
        return (self.input_shape[0], height, width, input_shape[3])
代码语言:javascript
复制
代码语言:javascript
复制
# Using our new custom layer with the Functional API
代码语言:javascript
复制
image_2= resize_layer(scale=2)(image, method="bilinear")

内置预处理

Keras带有几个在ImageNet上具有预训练的权重的模型,你可以直接使用它们。但是,如果你想直接使用这些模型,需要事先调整图像大小,因为最后完全连接层会强制固定输入大小。例如,Xception模型使用299×299的图像进行训练,那么所有图像都必须设置为大小以避免错误。除此之外,模型可能会有一些其他类型的你希望在向模型传递图像时自动应用它们的预处理或后处理。

我们可以使用Keras的Lambda层在模型中内置任何数学或预处理操作!lambda将简单地定义你要应用的操作。全层Lambda允许你将功能完全融入模型中。查看下面的代码,了解我们如何在模型中嵌入重新调整大小以及Xception的预处理!

代码语言:javascript
复制
from keras.applications.nasnetimport Xception, preprocess_input
代码语言:javascript
复制
from keras.modelsimport Sequential, Model
代码语言:javascript
复制
from keras.layers.coreimport Lambda
代码语言:javascript
复制
from keras.backendimport tf as ktf
代码语言:javascript
复制
代码语言:javascript
复制
# Initialize a Xception model
代码语言:javascript
复制
Xception_model= Xception(include_top=True, weights='imagenet', input_tensor=None, input_shape=None)
代码语言:javascript
复制
代码语言:javascript
复制
# Any required pre-processing should be baked into the model
代码语言:javascript
复制
input_tensor= Input(shape=(None,None,3))
代码语言:javascript
复制
x= Lambda(lambda image: ktf.image.resize_images(image, (299,299)))(input_tensor)
代码语言:javascript
复制
x= Lambda(lambda image: preprocess_input(image))(x)
代码语言:javascript
复制
output_tensor= Xception_model(x)
代码语言:javascript
复制
代码语言:javascript
复制
final_Xception_model= Model(input_tensor, output_tensor)

重复块的函数化

如果我们想要编写一个大型模型,比如50或甚至100层深的模型,代码就会变得非常混乱。当你必须定义极多的层,除非都是残差连接或稠密连接,否则你会发现代码极为散乱!

相反,我们实际上可以使用functional API的一个小技巧,将重复代码块定义为函数。例如,ResNet具有许多具有相同基本组件(批标准化,激活函数和卷积)的重复的残差块。因此,我们可以简单地将这些操作定义为函数中的一个块,从而极大地简化代码。查看下面的代码,它实现了ResNet和DenseNet块,并向你展示了如何使用它们。

view source

代码语言:javascript
复制
def preact_conv(inputs, k=3, filters=64):
代码语言:javascript
复制
    outputs= BatchNormalization()(inputs)
代码语言:javascript
复制
    outputs= Activation('relu')(outputs)
代码语言:javascript
复制
    outputs= Conv2D(filters, kernel_size=(k, k), padding='same',
代码语言:javascript
复制
                     kernel_initializer="glorot_normal")(outputs)
代码语言:javascript
复制
代码语言:javascript
复制
    return outputs
代码语言:javascript
复制
代码语言:javascript
复制
def ResidualBlock(inputs, kernal_size=3, filters=64):
代码语言:javascript
复制
    outputs= preact_conv(inputs, k=kernal_size, n_filters=filters)
代码语言:javascript
复制
    outputs= preact_conv(outputs, k=kernal_size, n_filters=filters)
代码语言:javascript
复制
代码语言:javascript
复制
    outputs= add([outputs, inputs])
代码语言:javascript
复制
    return outputs
代码语言:javascript
复制
代码语言:javascript
复制
def DenseBlock(stack, n_layers, growth_rate):
代码语言:javascript
复制
    new_features= []
代码语言:javascript
复制
    for iin range(n_layers):
代码语言:javascript
复制
        layer= preact_conv(stack, filters=growth_rate)
代码语言:javascript
复制
        new_features.append(layer)
代码语言:javascript
复制
        # stack new layer
代码语言:javascript
复制
        stack= concatenate([stack, layer], axis=-1)
代码语言:javascript
复制
    new_features= concatenate(new_features, axis=-1)
代码语言:javascript
复制
    return new_features
代码语言:javascript
复制
代码语言:javascript
复制
代码语言:javascript
复制
# Applying a stack of 5 Residual Blocks for a ResNet, just 5 lines of code
代码语言:javascript
复制
# If we wrote this out layer by layer, this would probably take 4-5x the number of lines
代码语言:javascript
复制
x= ResidualBlock(x)
代码语言:javascript
复制
x= ResidualBlock(x)
代码语言:javascript
复制
x= ResidualBlock(x)
代码语言:javascript
复制
x= ResidualBlock(x)
代码语言:javascript
复制
x= ResidualBlock(x)
代码语言:javascript
复制
代码语言:javascript
复制
# Applying a stack of 5 Dense Blocks for a DenseNet, just 5 lines of code
代码语言:javascript
复制
# DenseNets are even more complex to implements than ResNets, so if we wrote
代码语言:javascript
复制
# this out layer by layer, this would probably take 5-10x the number of lines
代码语言:javascript
复制
x= DenseBlock(x, n_layers=4, growth_rate=12)
代码语言:javascript
复制
x= DenseBlock(x, n_layers=6, growth_rate=12)
代码语言:javascript
复制
x= DenseBlock(x, n_layers=8, growth_rate=12)
代码语言:javascript
复制
x= DenseBlock(x, n_layers=10, growth_rate=12)
代码语言:javascript
复制
x= DenseBlock(x, n_layers=12, growth_rate=12)
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-11-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ATYUN订阅号 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 自定义度量和损失函数
  • 自定义层
  • 内置预处理
  • 重复块的函数化
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档