51

Tensorflow 笔记: Tensorflow 为什么难用

 4 years ago
source link: https://www.tuicool.com/articles/YZRnAry
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Tensorflow 笔记: Tensorflow 为什么难用

常常会听到人说 Tensorflow 很难用,大家就开始用 Pytorch 什么的,我不得不承认Tensorflow 很难用,但也有人很喜欢用。每次读 Google 发布的模型的代码 比如 berttensor2tensor 总发现作者把简单的事情写得很复杂。一个功能散在不同的文件里,不同的函数里。而Python又是动态语言,读起来就比较痛苦了。

Tensorflow 难用,而还是有很多人用,Google 也大力的推销。优势当然是有,我想搞清楚的是为什么难用,有没有什么最佳实践之类的东西。

读过很多人写的 Tensorflow 的代码,除了Google 那些家伙写的代码基本是一个风格之外,其他的都各有自己的操作。比如官方维护的 tensor2tensor 是建立在 Estimator 的 API 上的, 而且读起来是相当有难度的,因为首先得理解 Estimator 在实现抽象的时候定义的一些概念 比如 TrainSpec 是什么东西之类的。很遗憾的是Tensorflow 的文档也是相当的垃圾的,所以就导致我们需要去代码里看看到底在干啥。对于大部分人来说需要读源码才能用的工具自然就是难用的东西。

Tensorflow 的历史版本都有很大的差异,后面在升级的时候又会抛弃一些之前的API , 所以可能存在不兼容的情况。而且同一个功能会有不通 level 的实现。还有不同别名。举个例子吧。卷积是比较常用的了,我们可以用比较底层的 nn 模块中的方法 tf.nn.conv2d 而这个方法又会有不一样的名称,在1.14 的文档如下: 这个别称还算少的,多的时候有5到6个的。 6bQRjmA.jpg!web

把激活函数,和卷积放到一起实在 layers 的实现, 在 keras, slim 中又会有相应的实现,我用两种方式实现了下 LeNet5 一种是相对低级的 方式如下:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
#author: wu.zheng midday.me

"""
low level implement LeNet5

"""

import tensorflow as tf
import os
import mnist
import random
import numpy as np
import time

def create_lenet5_graph(x):
    # input_image_size : 32, 32 所有参数按原始论文写死
    w1 = tf.Variable(initial_value=tf.truncated_normal(shape=[5, 5, 1, 6], stddev=0.1 ), name="w1")
    b1 = tf.Variable(initial_value=tf.zeros(6), name='b1' )
    ### X: (None, 32, 32, 1)
    ### out:(None, 28, 28, 6)
    conv_1 = tf.nn.conv2d(input=x, filter=w1, strides=[1, 1, 1, 1],  padding='VALID')
    conv_1 = conv_1 + b1
    conv_1 = tf.nn.sigmoid(conv_1)
    ### out: (None, 14, 14, 6)
    pool_1 = tf.nn.avg_pool(value=conv_1, ksize=[1, 2, 2, 1],strides=[1, 2, 2, 1 ], padding="VALID")

    w2 = tf.Variable(initial_value=tf.truncated_normal(shape=[5, 5, 6, 16], stddev=0.1), name='w2')
    b2 = tf.Variable(initial_value=tf.zeros(16),  name="b2")
    ### out: (None, 10, 10, 16)
    conv_2 = tf.nn.conv2d(input=pool_1, filter=w2, strides=[1, 1, 1, 1], padding='VALID')
    conv_2= conv_2 + b2
    conv_2= tf.nn.sigmoid(conv_2)
    ### out:(None, 5, 5, 16)
    pool_2= tf.nn.avg_pool(value=conv_2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="VALID")
    shape = pool_2.get_shape().as_list()
    flated_pool = tf.reshape(pool_2, [shape[0], shape[1]*shape[2]*shape[3]])


    w3 = tf.Variable(initial_value=tf.truncated_normal(shape=[400, 120], stddev=0.1), name="W3" )
    b3 = tf.Variable(initial_value=tf.zeros(120), name='b3')
    fc_3 =  tf.matmul(flated_pool, w3)
    fc_3 = fc_3+ b3
    fc_3 = tf.nn.sigmoid(fc_3)

    w4 = tf.Variable(initial_value=tf.truncated_normal(shape=[120, 84], stddev=0.1), name='W4')
    b4 = tf.Variable(initial_value=tf.zeros(84), name='b4')
    fc_4 = tf.matmul(fc_3, w4)
    fc_4= fc_4 + b4
    fc_4 = tf.nn.sigmoid(fc_4)

    w5 = tf.Variable(initial_value=tf.truncated_normal(shape=[84, 10], stddev=0.1), name='W5')
    b5 = tf.Variable(initial_value=tf.zeros(10), name='b5')
    fc_5 = tf.matmul(fc_4, w5)
    fc_5 = fc_5 + b5
    variables = [w1, b1, w2, b2, w3, b3, w4, b4, w5, b5]
    return fc_5, variables

class Lenet5(object):
    def __init__(self, batch_size, mode='train', learning_rate=0.1):
        self.images = tf.placeholder(shape=[batch_size, 32, 32, 1], name='image', dtype=tf.float32)
        self.labels = tf.placeholder(shape=[batch_size, 10 ], name='labels', dtype=tf.float32)
        self.logits, self.variables = create_lenet5_graph(self.images)
        self.loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=self.logits, labels=self.labels))
        self.optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss=self.loss)
        self.predict_prob = tf.nn.softmax(self.logits)
        self.prediction = tf.argmax(self.predict_prob, axis=1)
        if mode == 'train' or mode=='eval':
            t_index = tf.argmax(self.labels, axis=1)
            equal = tf.equal(t_index, self.prediction)
            self.accuracy = tf.reduce_mean(tf.cast(equal, tf.float32))

这种方式看起来耦合了各种参数,而且很多代码重复,这就像是在用numpy 了。几乎有很多优化的方法,看看下面这个版本

#!/usr/bin/env python
#-*- coding:utf-8 -*-
#author: wu.zheng midday.me

import tensorflow as tf

def create_lenet5_graph(x):
    # x: (32,32, 1)
    conv1 = tf.layers.conv2d(
            inputs=x,
            filters=6,
            kernel_size=(5,5),
            padding='VALID',
            activation=tf.nn.relu,
            name='conv1',
            use_bias=True,
            kernel_initializer=tf.initializers.truncated_normal(mean=0.0, stddev=0.1))
    pool1 = tf.layers.average_pooling2d(inputs=conv1, pool_size=[2,2], strides=[1,1], name='pool1')

    conv2 = tf.layers.conv2d(
            inputs=pool1,
            filters=16,
            kernel_size=(5,5),
            padding='VALID',
            activation=tf.nn.relu,
            name='conv2',
            use_bias=True,
            kernel_initializer=tf.initializers.truncated_normal(mean=0.0, stddev=0.1))

    pool2 = tf.layers.average_pooling2d(inputs=conv2, pool_size=[2,2], strides=[1,1], name='pool2')
    pooled_shape = pool2.get_shape().as_list()
    flated_pool = tf.reshape(pool2, [pooled_shape[0], pooled_shape[1]*pooled_shape[2]*pooled_shape[3]])
    fc3 = tf.layers.dense(
            inputs=flated_pool,
            units=120,
            activation=tf.nn.sigmoid,
            kernel_initializer=tf.initializers.truncated_normal(mean=0.0, stddev=0.1),
            name='f3')
    fc4 = tf.layers.dense(
            inputs=fc3,
            units=84,
            activation=tf.nn.sigmoid,
            kernel_initializer=tf.initializers.truncated_normal(mean=0.0, stddev=0.1),
            name='f4')
    fc5 = tf.layers.dense(
            inputs=fc4,
            units=10,
            activation=None,
            kernel_initializer=tf.initializers.truncated_normal(mean=0.0, stddev=0.1),
            name='f5')
    return fc5

class Lenet5(object):
    def __init__(self, batch_size, mode='train', learning_rate=0.1):
        self.images = tf.placeholder(shape=[batch_size, 32, 32, 1], name='image', dtype=tf.float32)
        self.labels = tf.placeholder(shape=[batch_size, 10 ], name='labels', dtype=tf.float32)
        self.logits = create_lenet5_graph(self.images)
        self.loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=self.logits, labels=self.labels))
        self.optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss=self.loss)
        self.predict_prob = tf.nn.softmax(self.logits)
        self.prediction = tf.argmax(self.predict_prob, axis=1)
        if mode == 'train' or mode=='eval':
            t_index = tf.argmax(self.labels, axis=1)
            equal = tf.equal(t_index, self.prediction)
            self.accuracy = tf.reduce_mean(tf.cast(equal, tf.float32))

现在简单了些,但还不是最优,这里用了 layers 的 api ,layers 根据平常比较多的使用模式封装到了同一个API 里面,这种封装可能是一个 类可能是一个方法。这种方式一直优化下去,或许 Keras 的使用方式就是最简单的方式了。但是也可以看到越高层的API 就会更重,自然就失去很多灵活,增加很多复杂度,这时候就需要很强大的文档配合才能比较好的使用。

Tensorflow 的复杂度是最开始的架构设计上的一些缺陷,或者说是随着发展一个正常的状态。我们不需要去抱怨好用性,对于只是调包的来说清晰明了的文档很重要。但是没有文档的时候也要能读懂代码,这世界上不是任何东西都是按照你的想法转的。 Tensorflow 的优势自然在你逐渐的使用过程中体会到。那有没有最佳实践这种东西,我想是有的,只是没有标准就自然说什么是最好的。但是很多场景下会有一些规律的使用方法而已。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK