资讯专栏INFORMATION COLUMN

如何构建高可读性和高可重用的 TensorFlow 模型

wemall / 797人阅读

摘要:最值得注意的一点是,整个图都是在一个函数中定义和构造的,那么这即不可读也不可重复使用。

在 TensorFlow 中定义你的模型,可能会导致一个巨大的代码量。那么,如何去组织代码,使得它是一个高可读性和高可重用的呢?如果你刚刚开始学习代码架构,那么这里有一个例子,不妨学习一下。

定义计算图

当你设计一个模型的时候,从类出发是一个非常好的开始。那么如何来设计一个类的接口呢?通常,我们会为模型设计一些输入接口和输出目标值,并且提供训练接口,验证接口,测试接口等等。

class

 

Model

:

    

def

 __init__

(

self

,

 data

,

 target

):

        data_size 

=

 

int

(

data

.

get_shape

()[

1

])

        target_size 

=

 

int

(

target

.

get_shape

()[

1

])

        weight 

=

 tf

.

Variable

(

tf

.

truncated_normal

([

data_size

,

 target_size

]))

        bias 

=

 tf

.

Variable

(

tf

.

constant

(

0.1

,

 shape

=[

target_size

]))

        incoming 

=

 tf

.

matmul

(

data

,

 weight

)

 

+

 bias

        

self

.

_prediction 

=

 tf

.

nn

.

softmax

(

incoming

)

        cross_entropy 

=

 

-

tf

.

reduce_sum

(

target

,

 tf

.

log

(

self

.

_prediction

))

        

self

.

_optimize 

=

 tf

.

train

.

RMSPropOptimizer

(

0.03

).

minimize

(

cross_entropy

)

        mistakes 

=

 tf

.

not_equal

(

            tf

.

argmax

(

target

,

 

1

),

 tf

.

argmax

(

self

.

_prediction

,

 

1

))

        

self

.

_error 

=

 tf

.

reduce_mean

(

tf

.

cast

(

mistakes

,

 tf

.

float32

))

    

@property

    

def

 prediction

(

self

):

        

return

 

self

.

_prediction

    

@property

    

def

 optimize

(

self

):

        

return

 

self

.

_optimize

    

@property

    

def

 error

(

self

):

        

return

 

self

.

_error

基本上,我们都会使用 TensorFlow 提供的代码块来构建我们的模型。但是,它也存在一些问题。最值得注意的一点是,整个图都是在一个函数中定义和构造的,那么这即不可读也不可重复使用。

使用 @property 装饰器

如果你不了解装饰器,那么可以先学习这篇文章。

如果我们只是把代码分割成函数,这肯定是行不通的,因为每次调用函数时,图都会被新代码进行扩展。因此,我们要确保只有当函数第一次被调用时,这个操作才会被添加到图中。这个方式就是懒加载(lazy-loading,使用时才创建)。

class

 

Model

:

    

def

 __init__

(

self

,

 data

,

 target

):

        

self

.

data 

=

 data

        

self

.

target 

=

 target

        

self

.

_prediction 

=

 

None

        

self

.

_optimize 

=

 

None

        

self

.

_error 

=

 

None

    

@property

    

def

 prediction

(

self

):

        

if

 

not

 

self

.

_prediction

:

            data_size 

=

 

int

(

self

.

data

.

get_shape

()[

1

])

            target_size 

=

 

int

(

self

.

target

.

get_shape

()[

1

])

            weight 

=

 tf

.

Variable

(

tf

.

truncated_normal

([

data_size

,

 target_size

]))

            bias 

=

 tf

.

Variable

(

tf

.

constant

(

0.1

,

 shape

=[

target_size

]))

            incoming 

=

 tf

.

matmul

(

self

.

data

,

 weight

)

 

+

 bias

            

self

.

_prediction 

=

 tf

.

nn

.

softmax

(

incoming

)

        

return

 

self

.

_prediction

    

@property

    

def

 optimize

(

self

):

        

if

 

not

 

self

.

_optimize

:

            cross_entropy 

=

 

-

tf

.

reduce_sum

(

self

.

target

,

 tf

.

log

(

self

.

prediction

))

            optimizer 

=

 tf

.

train

.

RMSPropOptimizer

(

0.03

)

            

self

.

_optimize 

=

 optimizer

.

minimize

(

cross_entropy

)

        

return

 

self

.

_optimize

    

@property

    

def

 error

(

self

):

        

if

 

not

 

self

.

_error

:

            mistakes 

=

 tf

.

not_equal

(

                tf

.

argmax

(

self

.

target

,

 

1

),

 tf

.

argmax

(

self

.

prediction

,

 

1

))

            

self

.

_error 

=

 tf

.

reduce_mean

(

tf

.

cast

(

mistakes

,

 tf

.

float32

))

        

return

 

self

.

_error

这个代码组织已经比第一个代码好很多了。你的代码现在被组织成一个多带带的功能。但是,由于懒加载的逻辑,这个代码看起来还是有点臃肿。让我们来看看如何可以改进这个代码。

Python 是一种相当灵活的语言。所以,让我告诉你如何去掉刚刚例子中的冗余代码。我们将一个像 @property 一样的装饰器,但是只评估一次函数。它将结果存储在一个以装饰函数命名的成员中,并且在随后的调用中返回该值。如果你不是很了解这个装饰器是什么东西,你可以看看这个学习指南。

import

 functools

def

 lazy_property

(

function

):

    attribute 

=

 

"_cache_"

 

+

 

function

.

__name__

    

@property

    

@functools

.

wraps

(

function

)

    

def

 decorator

(

self

):

        

if

 

not

 hasattr

(

self

,

 attribute

):

            setattr

(

self

,

 attribute

,

 

function

(

self

))

        

return

 getattr

(

self

,

 attribute

)

    

return

 decorator

使用这个装饰器,我们可以将上面的代码简化如下:

class

 

Model

:

    

def

 __init__

(

self

,

 data

,

 target

):

        

self

.

data 

=

 data

        

self

.

target 

=

 target

        

self

.

prediction

        

self

.

optimize

        

self

.

error

    

@lazy_property

    

def

 prediction

(

self

):

        data_size 

=

 

int

(

self

.

data

.

get_shape

()[

1

])

        target_size 

=

 

int

(

self

.

target

.

get_shape

()[

1

])

        weight 

=

 tf

.

Variable

(

tf

.

truncated_normal

([

data_size

,

 target_size

]))

        bias 

=

 tf

.

Variable

(

tf

.

constant

(

0.1

,

 shape

=[

target_size

]))

        incoming 

=

 tf

.

matmul

(

self

.

data

,

 weight

)

 

+

 bias

        

return

 tf

.

nn

.

softmax

(

incoming

)

    

@lazy_property

    

def

 optimize

(

self

):

        cross_entropy 

=

 

-

tf

.

reduce_sum

(

self

.

target

,

 tf

.

log

(

self

.

prediction

))

        optimizer 

=

 tf

.

train

.

RMSPropOptimizer

(

0.03

)

        

return

 optimizer

.

minimize

(

cross_entropy

)

    

@lazy_property

    

def

 error

(

self

):

        mistakes 

=

 tf

.

not_equal

(

            tf

.

argmax

(

self

.

target

,

 

1

),

 tf

.

argmax

(

self

.

prediction

,

 

1

))

        

return

 tf

.

reduce_mean

(

tf

.

cast

(

mistakes

,

 tf

.

float32

))

请注意,我们在装饰器中只是提到了这些属性。完整的图还是要我们运行 tf.initialize_variables() 来定义的。

使用 scopes 来组织图

我们现在有一个简单的方法可以来定义我们的模型,但是由此产生的计算图仍然是非常拥挤的。如果你想要将图进行可视化,那么它将会包含很多互相链接的小节点。解决方案是我们对每一个函数(每一个节点)都起一个名字,利用 tf.namescope("name") 或者 tf.variablescope("name") 就可以实现。然后节点会在图中自由组合在一起,我们只需要调整我们的装饰器就可以了。

import

 functools

def

 define_scope

(

function

):

    attribute 

=

 

"_cache_"

 

+

 

function

.

__name__

    

@property

    

@functools

.

wraps

(

function

)

    

def

 decorator

(

self

):

        

if

 

not

 hasattr

(

self

,

 attribute

):

            

with

 tf

.

variable_scope

(

function

.

__name

):

                setattr

(

self

,

 attribute

,

 

function

(

self

))

        

return

 getattr

(

self

,

 attribute

)

    

return

 decorator

我给了装饰器一个新的名字,因为除了我们加的懒惰缓存,它还具有特定的 TensorFlow 功能。除此之外,模型看起来和原来的是一样的。


欢迎加入本站公开兴趣群

商业智能与数据分析群

兴趣范围包括各种让数据产生价值的办法,实际应用案例分享与讨论,分析工具,ETL工具,数据仓库,数据挖掘工具,报表系统等全方位知识

QQ群:81035754

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/4685.html

相关文章

  • TensorFlow Hub介绍:TensorFlow中可重用机器学习模块库

    摘要:机器学习模型内部的组成部分,可以使用进行打包和共享。为机器学习开发者提供库产生了库。库是一个在中进行发布和重用中机器学习模块的平台。 摘要: 本文对TensorFlow Hub库的介绍,并举例说明其用法。 在软件开发中,最常见的失误就是容易忽视共享代码库,而库则能够使软件开发具有更高的效率。从某种意义上来说,它改变了编程的过程。我们常常使用库构建块或模块,并将其连接在一起进行编程。 开...

    sunny5541 评论0 收藏0
  • Keras vs PyTorch:谁是「第一」深度学习框架?

    摘要:第一个深度学习框架该怎么选对于初学者而言一直是个头疼的问题。简介和是颇受数据科学家欢迎的深度学习开源框架。就训练速度而言,胜过对比总结和都是深度学习框架初学者非常棒的选择。 「第一个深度学习框架该怎么选」对于初学者而言一直是个头疼的问题。本文中,来自 deepsense.ai 的研究员给出了他们在高级框架上的答案。在 Keras 与 PyTorch 的对比中,作者还给出了相同神经网络在不同框...

    _DangJin 评论0 收藏0
  • 使用 "5W1H" 写出可读 Git Commit Message

    摘要:共字,读完需分钟。下面提出一种可以帮你写出高可读的实践方法,这个方法并非原创,最早的实践来自于这篇文章。本文作者王仕军,商业转载请联系作者获得授权,非商业转载请注明出处。 showImg(https://segmentfault.com/img/remote/1460000009341335?w=1240&h=403); 共 1926 字,读完需 4 分钟。所有工程师都知道,代码是编写...

    DevYK 评论0 收藏0
  • 实现 TensorFlow 架构规模性和灵活性

    摘要:是为了大规模分布式训练和推理而设计的,不过它在支持新机器学习模型和系统级优化的实验中的表现也足够灵活。本文对能够同时兼具规模性和灵活性的系统架构进行了阐述。尽管大多数训练库仍然只支持,但确实能够支持有效的推理。 TensorFlow 是为了大规模分布式训练和推理而设计的,不过它在支持新机器学习模型和系统级优化的实验中的表现也足够灵活。 本文对能够同时兼具规模性和灵活性的系统架构进行了阐述。设...

    RiverLi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<