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

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

内容简介:沿着之前的这次围绕着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


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

查看所有标签

猜你喜欢:

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

计算广告

计算广告

刘鹏、王超 / 人民邮电出版社 / 2015-9-1 / 69.00元

计算广告是一项新兴的研究课题,它涉及大规模搜索和文本分析、信息获取、统计模型、机器学习、分类、优化以及微观经济学等诸多领域的知识。本书从实践出发,系统地介绍计算广告的产品、问题、系统和算法,并且从工业界的视角对这一领域具体技术的深入剖析。 本书立足于广告市场的根本问题,从计算广告各个阶段所遇到的市场挑战出发,以广告系统业务形态的需求和变化为主线,依次介绍合约广告系统、竞价广告系统、程序化交易......一起来看看 《计算广告》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换