内容简介:常常会听到人说 Tensorflow 很难用,大家就开始用 Pytorch 什么的,我不得不承认Tensorflow 很难用,但也有人很喜欢用。每次读 Google 发布的模型的代码 比如Tensorflow 难用,而还是有很多人用,Google 也大力的推销。优势当然是有,我想搞清楚的是为什么难用,有没有什么最佳实践之类的东西。读过很多人写的 Tensorflow 的代码,除了Google 那些家伙写的代码基本是一个风格之外,其他的都各有自己的操作。比如官方维护的 tensor2tensor 是建立在 E
Tensorflow 笔记: Tensorflow 为什么难用
常常会听到人说 Tensorflow 很难用,大家就开始用 Pytorch 什么的,我不得不承认Tensorflow 很难用,但也有人很喜欢用。每次读 Google 发布的模型的代码 比如 bert , tensor2tensor 总发现作者把简单的事情写得很复杂。一个功能散在不同的文件里,不同的函数里。而 Python 又是动态语言,读起来就比较痛苦了。
Tensorflow 难用,而还是有很多人用,Google 也大力的推销。优势当然是有,我想搞清楚的是为什么难用,有没有什么最佳实践之类的东西。
读过很多人写的 Tensorflow 的代码,除了Google 那些家伙写的代码基本是一个风格之外,其他的都各有自己的操作。比如官方维护的 tensor2tensor 是建立在 Estimator 的 API 上的, 而且读起来是相当有难度的,因为首先得理解 Estimator 在实现抽象的时候定义的一些概念 比如 TrainSpec 是什么东西之类的。很遗憾的是Tensorflow 的文档也是相当的垃圾的,所以就导致我们需要去代码里看看到底在干啥。对于大部分人来说需要读源码才能用的 工具 自然就是难用的东西。
Tensorflow 的历史版本都有很大的差异,后面在升级的时候又会抛弃一些之前的API , 所以可能存在不兼容的情况。而且同一个功能会有不通 level 的实现。还有不同别名。举个例子吧。卷积是比较常用的了,我们可以用比较底层的 nn
模块中的方法 tf.nn.conv2d
而这个方法又会有不一样的名称,在1.14 的文档如下: 这个别称还算少的,多的时候有5到6个的。
把激活函数,和卷积放到一起实在 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 的优势自然在你逐渐的使用过程中体会到。那有没有最佳实践这种东西,我想是有的,只是没有标准就自然说什么是最好的。但是很多场景下会有一些规律的使用方法而已。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【每日笔记】【Go学习笔记】2019-01-04 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-02 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-07 Codis笔记
- vue笔记3,计算笔记
- Mysql Java 驱动代码阅读笔记及 JDBC 规范笔记
- 【每日笔记】【Go学习笔记】2019-01-16 go网络编程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。