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 的优势自然在你逐渐的使用过程中体会到。那有没有最佳实践这种东西,我想是有的,只是没有标准就自然说什么是最好的。但是很多场景下会有一些规律的使用方法而已。
- 用CAD画角度都有哪些方法?三种方法教你用CAD画角度用CAD绘图,水平或者竖直的直线不是全部,有的直线有一定角度,那么怎么在CAD中画角度?下面cad画角度三种方法就会全部给大家介绍出来,希望对大...
- 夭的组词大全(约50个) 夭的词语解释_夭是什么意思?以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 夭的拼音 夭的解释 夭是什么意...
- 百威啤酒的度数啤酒是世界上最古老的酒精饮料之一,而且啤酒也有着悠久的历史,在世界上有各种各样的啤酒文化,在各个国家人们对啤酒的喜爱都是很深的...
- 当发现男友的胸变得越来越大,那你得警惕了原创 木子九 第十一诊室 图片: 黄仔 | 撰稿: 木子九 | 责编: 浮游炮 原创文章,未经许可,请勿转载 大家好哇~今天是木子九值班! 「忍一时卵...
- 《世说新语•言语第二》105、蝴琏接神之器谢混问羊孚:“为什么孔子说子贡为‘器’时要说他是瑚琏?”羊孚说:“自然是因为蝴琏是迎神的器具。” 【原文】 谢混问羊孚:“何以器...
- 借条到期后怎么写延期范本一、借条到期后怎么写延期范本 关于借条续期的书写方式说明如下:在借贷关系所约定的期限届满之后,如借款人未能按照约定履行还款义务,...
- 如何尝试修复显卡故障(完整教程)如何尝试修复显卡故障(完整教程) 目录 第一部分:起步 1. 引言 2. 了解机型和问题 3. 确定故障原因 第二部分:修复干坏的GPU 1. 了解GPU故障代...
- 家常羊蝎子的做法羊蝎子汆水后热水洗净,入热水锅,开大火煮。 炒锅内入大葱、桂皮、八角、生姜、花椒、小茴香,无油小火炒香。 炒香的辅料倒入汤锅,水...
- 2019年好下款的网贷平台合集!这几个不错!现在很多人每天都会面临各种压力,尤其是缺钱的问题,临时性的***是最麻烦的,所以不少人会求助于网贷的帮助。由于网贷平台非常多,所以...
- 王者战绩显示多久的战绩(王者荣耀中战绩显示的场次限制是多少?)在《王者荣耀》这款热门的多人在线战斗竞技游戏中,玩家们常常关注自己的战绩记录。了解战绩显示的时间范围对于玩家评估自己的游戏表现...