让Keras更酷一些!Keras模型杂谈(二)

栏目: 数据库 · 发布时间: 6年前

内容简介:沿着之前的这次围绕着Keras的loss、metric、权重和进度条进行展开。一般我们用Keras定义一个模型,是这样子的:

沿着之前的 《让Keras更酷一些!Keras模型杂谈》 ,让Keras来得更有趣些吧~

这次围绕着Keras的loss、metric、权重和进度条进行展开。

一般我们用Keras定义一个模型,是这样子的:

x_in = Input(shape=(784,))
x = x_in
x = Dense(100, activation='relu')(x)
x = Dense(10, activation='softmax')(x)

model = Model(x_in, x)
model.compile(loss='categorical_crossentropy ',
              optimizer='adam',
              metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5)

这种模型就是普通的输入输出结构,然后loss是输出的运算。然而,对于比较复杂的模型,如自编码器、GAN、Seq2Seq,这种写法有时候不够方便,loss并不总只是输出的运算。幸好,比较新的Keras版本已经支持更加灵活的loss定义,比如我们可以这样写一个自编码器:

x_in = Input(shape=(784,))
x = x_in
x = Dense(100, activation='relu')(x)
x = Dense(784, activation='sigmoid')(x)

model = Model(x_in, x)
loss = K.mean((x - x_in)**2)
model.add_loss(loss)
model.compile(optimizer='adam')
model.fit(x_train, None, epochs=5)

上述写法的几个特点是:

1、 compile 的时候并没有传入loss,而是在 compile 之前通过另外的方式定义loss,然后通过 add_loss 加入到模型中,这样可以随意写足够灵活的loss,比如这个loss可以跟中间层的某个输出有关、跟输入有关,等等。

2、 fit 的时候,原来的目标数据,现在是 None ,因为这种方式已经把所有的输入输出都通过 Input 传递进来了。读者还可以看我之前写的 Seq2Seq《玩转Keras之seq2seq自动生成标题》 ,在那个例子中,读者能更充分地感觉到这种写法的便捷性。

这里的metric,指的是衡量模型性能的一些指标,比如正确率、F1等,Keras内置了一些 常见的metric 。像开头例子的 accuracy 一样,将这些metric的名字加入到 model.compile 中,就可以在训练过程中动态地显示这些metric。

当然,你也可以参考Keras中内置的metric来自己定义新metric,但问题是在标准的metric定义方法中,metric是“输出层”与“目标值”之间的运算结果,而我们经常要在训练过程中观察一些特殊的量的变化过程,比如我想观察中间某一层的输出变化情况,这时候标准的metric定义就无法奏效了。

那可以怎么办呢?我们可以去看Keras的源码,去追溯它的metric相关的方法,最终我发现metric实际上定义在两个 list 之中,通过修改这两个 list ,我们可以非常灵活地显示需要观察的metric,比如

x_in = Input(shape=(784,))
x = x_in
x = Dense(100, activation='relu')(x)
x_h = x
x = Dense(10, activation='softmax')(x)

model = Model(x_in, x)
model.compile(loss='categorical_crossentropy ',
              optimizer='adam',
              metrics=['accuracy'])

# 重点来了
model.metrics_names.append('x_h_norm')
model.metrics_tensors.append(K.mean(K.sum(x_h**2, 1)))

model.fit(x_train, y_train, epochs=5)

上述代码展示了在训练过程中观察中间层的平均模长的变化情况。可以看到,主要涉及到两个 listmodel.metrics_names 是metric的名称,是字符串列表; model.metrics_tensors 是metric的张量。只要在这里把你需要展示的量添加进去,就可以在训练过程中显示了。当然,要注意的是,一次性只能添加一个标量。

有时候我们需要把权重做一些约束,常见的是归一化,如L2范数归一化、谱归一化等,当然也可以是其他约束。

权重约束的实现方法一般有两种。第一种是事后处理,即在每一步梯度下降之后直接对权重进行硬处理,即

\begin{equation}\begin{aligned}&\boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \nabla_{\boldsymbol{\theta}}L(\boldsymbol{\theta})\\

&\boldsymbol{\theta}\leftarrow constraint(\boldsymbol{\theta})\end{aligned}\end{equation}

显然,这种处理方法是要被写在优化器的实现中的。而事实上Keras内置的就是这一种,使用方法很简单,只需要在添加层的时候,设置 kernel_constraintbias_constraint 参数即可,详细请参考: https://keras.io/constraints/

第二种是事前处理,我们希望对权重处理后,才代入后续的层进行运算,也就是说把约束作为模型的一部分,而不是作为优化器的一部分。Keras本身不提供这种方案的支持,但我们可以自行实现这个需求。

这时候Keras设计的精妙之处就充分体现出来了。在建立一个层对象的时候,Keras将它分为两个步骤: buildcall ,前者负责建立权重,后者负责进行运算。默认情况下,这两个部分是同时执行的,但是我们可以“移花接木”,让我们手动分步执行。

下面是利用这个思路实现的 谱归一化(Spectral Normalization)

class SpectralNormalization:
    """层的一个包装,用来加上SN。
    """

    def __init__(self, layer, **kwargs):
        self.layer = layer

    def spectral_norm(self, w, r=5):
        w_shape = K.int_shape(w)
        in_dim = np.prod(w_shape[:-1]).astype(int)
        out_dim = w_shape[-1]
        w = K.reshape(w, (in_dim, out_dim))
        u = K.ones((1, in_dim))
        for i in range(r):
            v = K.l2_normalize(K.dot(u, w))
            u = K.l2_normalize(K.dot(v, K.transpose(w)))
        return K.sum(K.dot(K.dot(u, w), K.transpose(v)))

    def spectral_normalization(self, w):
        return w / self.spectral_norm(w)

    def __call__(self, inputs):
        if not self.layer.built:
            input_shape = K.int_shape(inputs)
            self.layer.build(input_shape)
            self.layer.built = True
        if not hasattr(self.layer, 'spectral_normalization'):
            if hasattr(self.layer, 'kernel'):
                self.layer.kernel = self.spectral_normalization(self.layer.kernel)
            if hasattr(self.layer, 'gamma'):
                self.layer.gamma = self.spectral_normalization(self.layer.gamma)
            self.layer.spectral_normalization = True
        return self.layer(inputs)

使用方法为

x = SpectralNormalization(Dense(100, activation='relu'))(x)

也就是定义完层之后加个 SpectralNormalization 修改一下就行了。至于原理,我们只需要观察 __call__ 部分,首先新建立的层是 built=False 的,然后我们自己手动执行 build 方法,然后对原来的权重进行归一化,并赋值覆盖原来的权重,即 self.layer.kernel = self.spectral_normalization(self.layer.kernel) 这一句。

调用Keras的进度条

最后提一个比较有趣的玩意,就是Keras自带的进度条,早期就是Keras这个自带的进度条吸引了不少新用户。当然,现在来说进度条已经不是什么新鲜玩意了,Python下有很好用的进度条工具tqdm,很久之前就介绍过它: 《两个惊艳的 python 库:tqdm和retry》 。

当然,如果你更喜欢Keras进度条的样式,或者不想另外安装tqdm,那么也可以在自己的设计中调用Keras的进度条:

import time
from keras.utils import Progbar

pbar = Progbar(100)
for i in range(100):
    pbar.update(i + 1)
    time.sleep(0.1)

它会显示进度和剩余时间,如果要在进度条上更多内容,可以在 update 的时候增加 value 参数,比如

import time
from keras.utils import Progbar

pbar = Progbar(100)
for i in range(100):
    pbar.update(i + 1, values=[('something', i - 10)])
    time.sleep(0.1)

不过要注意的是,这里的value是会滑动平均的,因为这是Keras为metric设计的而已,如果你不想它滑动更新,那就

import time
from keras.utils import Progbar

pbar = Progbar(100, stateful_metrics=['something'])
for i in range(100):
    pbar.update(i + 1, values=[('something', i - 10)])
    time.sleep(0.1)

更多使用参数可以参考 这里 。总的来说,功能远不如tqdm强,但是作为一个精致的工具,偶尔使用一下,还是个不错的选择。

又分享了一些花式Keras技巧,希望对大家有帮助。灵活地用好Keras是一件颇有趣味的事情,Keras也许不是最好的深度学习框架,但应该是最优雅的框架(封装),而且很可能没有之一。

DL苦短,我用Keras~

转载到请包括本文地址: https://kexue.fm/archives/6311

如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。

如果您觉得本文还不错,欢迎/本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!

如果您需要引用本文,请参考:

苏剑林. (2019, Jan 27). 《 让Keras更酷一些!Keras模型杂谈(二) 》[Blog post]. Retrieved from https://kexue.fm/archives/6311


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Pro JavaScript Techniques

Pro JavaScript Techniques

John Resig / Apress / 2006-12-13 / USD 44.99

Pro JavaScript Techniques is the ultimate JavaScript book for the modern web developer. It provides everything you need to know about modern JavaScript, and shows what JavaScript can do for your web s......一起来看看 《Pro JavaScript Techniques》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具