tensorflow 学习笔记¶
介绍¶
tensorflow 可用于c、c++和python
最好是从源码安装(在开发android app时要求)
- 生成动态链接库(生成的文件在 bazel-bin/tensoflow 下)
# ubuntu(.so) bazel build //tensoflow:libtensorflow.so # mac(.dylib) bazel build //tensorflow
- 工作流程包括以下几大块:
construction phase ——> assembles a graph
execution phase ——> session
save & load ——> saver, superviser (GraphDef)
visuable the graph ——> summary, tensorboard
Install¶
ubuntu Tensorflow
从构建模型到Android端¶
训练阶段¶
1 构建模型时注意事项¶
- tf.app.flag
不建议使用,未来可能修改,建议使用python的 argparse module 实现自己的flag parsing
- build a graph that works with variable batch sizes
使用 placeholder 时将 shape 的第一位设为 None,此时注意构建 model 时的 reshape 问题,将相应位置设为 -1
- 网络复用
定义时使用 tf.get_variable() 以检测 variable 是否重复;在定义网络时,在 scope 命名的名称下定义每个子网络,可实现复用;在 validate 时,设置reuse,那么可定义一个测试网络,其参数与 inference 一致;警告
reuse 默认为 False,只能由 False 设置为 True, 其后保持该状态,无法从 True 设置为 False
1.1 add new op to TF-platform¶
tensoflow/core/ops 中注册 op ( _ops.cc 中
REGISTER_OP
for op and grad)tensoflow/core/kernels 下实现 op ( _op.cc, _op_gpu.cu.cc, _op_gpu.h)
tensoflow/core/ops/ops.pbtxt 中添加 op 的定义
tensoflow/core/BUILD 的
ops
、all_kernels
中声明前述定义tensorflow/core/kernels/BUILD 相应
cc_library
,tf_kernel_library ``, ``filegroup
tensoflow/python/ops 中声明 (.py @@, _grad.py @ops.RegisterGradient(“XXX”))
tensorflow/contrib/makefile/tf_op_files.txt 加入op kernels 所在路径
重新编译
bazel clean ./configure bazel build ...
1.2 data input pipline¶
生成 tfrecord 时的压缩问题,图片做tf.image.encode_XXX,
读取 tfrecord 时注意keys_to_feature 每条的格式,图片读出规格,
读成 batch 注意前面各步骤是否导致图片大小不可知
2 模型&数据的保存和导入¶
2.1 Saver(re:cifar10)¶
saver = tf.train.Saver() saver.save(sess,FLAG.checkpoint_dir + 'xxx.ckpt', global_step = #) # saver.save(..., write_meta_graph=False) 可在保存 .ckpt 文件时不保存 .meta文件 saver.load() # restore the weights ckpt = tf.train.get_checkpoint_state(FLAG.checkpoint_dir) if ckpt and ckpt.model_checkpoint_path: saver.restore(sess, ckpt.model_checkpoint_path)Saver 将 model 中的 Variable 保存为 checkpoints(.ckpt)
如果只想保存/导入部分参数,在定义 saver 时指定 var_list 参数,详情参考 API 文档
By default, a tf.train.Saver will create ops that (i) save every variable in your graph when you call saver.save() and (ii) lookup (by name) every variable in the given checkpoint when you call saver.restore(). While this works for most common scenarios, you have to provide more information to work with specific subsets of the variables:
If you only want to restore a subset of the variables, you can get a list of these variables by calling
tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope=G_NETWORK_PREFIX)
, assuming that you put the “g” network in a common withtf.name_scope(G_NETWORK_PREFIX):
ortf.variable_scope(G_NETWORK_PREFIX):
block. You can then pass this list to thetf.train.Saver
constructor.If you want to restore a subset of the variable and/or they variables in the checkpoint have different names , you can pass a dictionary as the
var_list
argument. By default, each variable in a checkpoint is associated with a key, which is the value of itstf.Variable.name
property. If the name is different in the target graph (e.g. because you added a scope prefix), you can specify a dictionary that maps string keys (in the checkpoint file) totf.Variable
objects (in the target graph).save model:
w1 = tf.Variable(tf.truncated_normal(shape=[10]), name='w1') w2 = tf.Variable(tf.truncated_normal(shape=[20]), name='w2') tf.add_to_collection('vars', w1) tf.add_to_collection('vars', w2) saver = tf.train.Saver() sess = tf.Session() sess.run(tf.global_variables_initializer()) saver.save(sess, 'my-model') # `save` method will call `export_meta_graph` implicitly. # you will get saved graph files:my-model.metarestore model:
sess = tf.Session() new_saver = tf.train.import_meta_graph('my-model.meta') new_saver.restore(sess, tf.train.latest_checkpoint('./')) all_vars = tf.get_collection('vars') for v in all_vars: v_ = sess.run(v) print(v_)注解
训练时定义 saver 需在构建好网络后, 设置训练参数(如 lr, momentum 等)前, 否则模型无关的参数会加入到 saver 的 参数列表中,当从已训练好的模型中导入参数时会报错!或者定义 saver 时, 指定参数列表。
如果保存的模型文件随着step 增大, 可能是在训练loop 中有 add op 的操作,导致graph 随训练增大 —— Try printing out the node names in the graph each step to checkout. Or you can do “tf.get_default_graph().finalize()” after you’ve done modifying your graph, that’ll throw an error on any new modifications
2.2 Supervisor ref1¶
# restore the model tf.import_graph_defSupervisor 将 model definition 保存为 graph.pbtxt
- Note:
由于 Saver 使用 special collection holding list of Variables that’s attached to the model Graph, 而import_graph_def 时不会初始化这个 collection ,因此这二者不能一起使用;
目前可以使用 Pyan Sepassi 的方法——构造Graph时为每个 node 添加名字,然后使用 Saver 导入 weights;
如果要一起使用,在创建Variable的时候,对每个Variable对象使用 tf.add_to_collection(tf.GraphKeys.VARIABLES,variable);
2.3 同时保存模型和数据¶
- 思路:
先使用 tf.import_graph_def 的 constants 替换 original(training)graph 中的 variables;然后使用 tf.Graph.as_graph_def write out the resulting GraphDef- 具体步骤如下:
构建并训练自己的模型 tf.Graph (记为 g_1);
提取variable最终的值并存储为 numpy array (使用session.run());
在新的 tf.Graph (记为g_2)中为每个 variable 创建 tf.constant(),并用步骤2 的值赋值;
使用 tf.import_graph_def 将 g_1 的节点拷贝到 g_2,利用参数 input_map ,将g_1 中的variable 替换为步骤3中创建的 tf.constant();
使用 g_2.as_graph_def 保存 graph 的 protocol buffer representation。
方法1
采用“/tensorflow/python/tools/freeze_graph.py”脚本,参考 A tool…
- 步骤如下:
Save the checkpoints. (This is important as all your trained variables reside here)
Save the graph definition (raw definition, no variable).
Use the freeze_graph file to combine the graph structure(step2) with the valuse of each nodes values(step2) and generate a new graph model
方法2
serialize your minimal graph both into textual(.pbtxt) and binary(.pb) protobuf by code:
from tensorflow.python.framework.graph_util import convert_variables_to_constants minimal_graph = convert_variables_to_constants(sess,sess.graph_def,["output"]) #"output" is the name of prediction node tf.train.write_graph(minimal_graph,'.','xxx.proto', as_text=False) tf.train.write_graph(minimal_graph,'.','xxx.txt', as_text=True) # restore the model tf.import_graph_def # serialize the graph tf.Graph.as_graph_def
- convert_variables_to_constants 做了两件事:
it freeze the weights by replacing variables with constantsit removes nodes which are not related to feedforward prediction这个方法能够减小保存的 .pb 文件的大小,将不必要的内容删除;直接保存 .pb 会将 tensorflow 的所有方法加载
3 可视化(tensorboard)¶
保存信息:
#在下一语句前至少调用一次本类语句,否则报错 tf.scalar_summary("name",variablename) tf.image_summary("name",imagename) summary_op = tf.merge_all_summaries() # 注意,本句不能放到训练循环内,否则会导致meta文件不断增大 # 或者: loss_summary = tf.scalar_summary(...) summary_op = tf.merge_summary([loss_sum]) summary_writer = tf.train.SummaryWriter('/path/to/logs',sess.graph) summary_str = sess.run(summary_op) # 根据情况,有些必须要feed_dict summary_writer.add_summary(summary_str,global_step=step) summary_writer.close() #必须加,否则没有东西显示显示:
# pip 安装 $ tensorboard --logdir=/path/to/logs/ # 源码安装 $ python tensorflow/tensorboard/tensorboard.py --logdir=/path/to/logs/通过 sshdf 连接查看时,相对路径不起作用,要退到~, 然后使用绝对路径
cd ~ tensorboard --logdir=<abs_dir>注解
每次保存前要将 logs 中的文件删除,否则出现混乱。
4 对已训练好模型的操作¶
4.1 Changing Inputs in existing networks ref2¶
The tf.import_graph_def()
function provides the only (supported) way to perform this surgery, via the optional input_map
argument. Let’s say you want to replace the tensor “DecodeJpeg:0” with your new variable. You would do something like the following:
graph_def = ...
tf_new_image = tf.constant(...)
_ = tf.import_graph_def(graph_def, input_map={"DecodeJpeg:0": tf_new_image})
4.2 将已有网络作为现有网络的一部分重新训练网络 ref3¶
若要保留原有参数,optimize 时将欲保留的参数除外,如下:
opt.minimize(loss, <subset of variables you want to train>)
否则导入参数后正常训练即可
4.3 retrain models
5 set layer-wise learning rate¶
How to set layer-wise learning rate in Tensorflow?
var_list1 = [variables from first 5 layers] var_list2 = [the rest ofvariables] opt1 = tf.train.GradientDescentOptimizer(0.00001) opt2 = tf.train.GradientDescentOptimizer(0.0001) grads = tf.gradients(loss, var_list1 + var_list2) grads1 = grads[:len(var_list1)] grads2 = grads[len(var_list1):] tran_op1 = opt1.apply_gradients(zip(grads1, var_list1)) train_op2 = opt2.apply_gradients(zip(grads2, var_list2)) train_op = tf.group(train_op1, train_op2)
测试阶段¶
1 .pb文件的导入(re:classify_image)¶
- 有两种表达方式:
from tensorflow.python.platform import gfile # 第1种方法 (当需要构建network时,此法不可用?) with tf.Session() as sess: with gfile.FastGFile('/path/to/.pbfile','rb') as f: # 也可以是tf.gfile.FastGFile graph_def = tf.GraphDef() graph_def.ParseFromString(f.read()) sess.graph.as_default() tf.import_graph_def(graph_def) # 第2种方法 (node 名称前会加上 import) with tf.Graph().as_default() as imported_graph: with gfile.FastGFile('/path/to/.pbfile','rb') as f: graph_def = tf.GraphDef() graph_def.ParseFromString(f.read()) tf.import_graph_def(graph_def) sess = tf.Session(graph = imported_graph)- 使用时,为确定调用名称,使用以下语句查看:
for node in session.graph_def.node: print node.name for op in tf.get_default_graph().get_operations(): print op.name for op in sess.graph.get_operations(): # 此句同上 print op.name softmax_tensor = sess.graph.get_tensor_by_name("XXX") prediction = sess.run(softmax_tensor,{"name",input})
C++ 开发¶
1 创建graph并使用¶
... // root 添加一系列ops tensoflow::GraphDef graph_def; TF_RETURN_IF_ERROR(root.ToGraphDef(&graph_def)); std::unique_ptr<tensorflow::Session> session( tensorflow::NewSession(tensorflow::SessionOptions())); TF_RETURN_IF_ERROR(session->Create(graph_def)); TF_RETURN_IF_ERROR(session->Run({}, # input {output_name} # which node we want get the output from , {}, out_tensors # where to put the output data ));
2 .pb文件的使用(re:label_image)¶
// 方法1: Status LoadGraph(string graph_file_name, Session** session) { tensoflow::GraphDef graph_def; TF_RETURN_IF_ERROR( ReadBinaryProto(tensoflow::Env::Default(), graph_file_name, &graph_def) ); TF_RETURN_IF_ERROR(NewSession(SessionOptions(), session)); TF_RETURN_IF_ERROR((*session)->Create(graph_def)); return Status::OK(); } // 方法2: Status LoadGraph(string graph_file_name, std::unique_ptr<tensoflow::Session>* session) { tensoflow::GraphDef graph_def; TF_RETURN_IF_ERROR( ReadBinaryProto(tensoflow::Env::Default(), graph_file_name, &graph_def) ); session->reset(tensoflow::NewSession(tensoflow::SessionOptions())); TF_RETURN_IF_ERROR((*session)->Create(graph_def)); return Status::OK(); }
- 编译注意事项:
BUILD 文件中的内容(比如文件name 与 .cc 文件一致)
整个项目文件夹要放在” tensorflow/tensorflow”以下的文件夹中
LISCENCE 文件??
不要和其它无关文件放在一起
单幅图时注意expand dims
3 生成 tensorflow 的动态链接库¶
Create a new folder in the TensorFlow repo at tensorflow/tensorflow/libtensorflow/.
tensorflow/tensorflow/libtensorflow/ tensorflow/tensorflow/libtensorflow/BUILDInside this folder we’re going to create a new BUILD file which will contain a single call to cc_binary with the linkshared option set to 1 so that we get a .so from the build. The name of the binary must end in .so or it will not work.
cc_binary( name = "libtensorflow.so", linkshared = 1, deps = [ "//tensorflow/core:tensorflow", ] )From the root of the repository, run
./configure
.Compile the shared library with
bazel build --config=opt //tensorflow/libtensorflow:libtensorflow.so
and locate the generated file from the repo’s root:bazel-bin/tensorflow/libtensorflow/libtensorflow.so
注解
If you’re on OS X and using Node.js you’ll need to rename the shared library from libtensorflow.so to libtensorflow.dylib
if compile with GPU, cmd is
bazel build --config=opt --config=cuda //tensorflow/libtensorflow:libtensorflow.so
4 c++ 代码调试¶
在编译时加入调试信息
bazel build -c dbg //<path to src>:<target_name>
, 然后使用 gdb 调试更多调试, 参考 TENSORFLOW_DEBUG.md
Android实现¶
android环境搭建¶
若tensorflow尚未下载,最好用以下方法下载:
$ git clone https://github.com/tensorflow/tensorflow.git --recurse-submodules
后续参考“/tensorflow/example/android”中的 Readme.md 文件(国内sdk 和 ndk 的下载地址: androiddevtools )
编译生成的.so文件(位于.cache文件夹下)可用于androd studio中开发使用。
小技巧
可以通过查看BUILD文件查询.so文件的名称,后通过locate命令查询其在ubuntu中的位置。
tensorflow的mobile实现: mobile <https://www.tensorflow.org/mobile.html> _
有些错误可能是因为环境变量的设置问题,例如报错 /usr/local/bin/gcc ,将/usr/local/bin 从PATH中删除即可,猜测可能是gcc冲突之类的原因。注意修改环境变量后确认其是否真正生效!!
在反复修改编译的过程中建议使用
bazel clean
来清除前次的编译结果,避免未知错误bazel 版本造成问题,加 –invcompatible_load_argument_is_label=false
找不到cuda等的.so文件,build 加 –action_env=”LD_LIBRARY_PATH=${LD_LIBRARY_PATH}”
注解
若之前用于编译生成gpu 版的.whl 文件,那么此时注意重新configure,因为android 代码的编译不需要 gpu。 the android demo should not need CUDA. Is it possible during configuration you configured TF to build with CUDA? That would add –config=cuda automatically to the build.
开发¶
bazel build 后生成 .so 文件(在bazel-out|.cache文件夹下),可将其用于AS中开发。
小技巧
可以通过查看BUILD文件查询.so文件的名称,后通过locate命令查询其在ubuntu中的位置。
参考 Android TensorFlow Machine Learning Example
根据
tensoflow/contrib/android
下的README
build the .jar and .so file.create an android sample project in Android Studio.
Put label file (imagenet_comp_graph_label_strings.txt) and pre-trained model (tensorflow_inception_graph.pb) into
assets
folder.Put .jar file in
libs
folder and right click and add as library.Create
jniLibs
folder inmain
directory and put .so injniLibs/armeabi-v7a
folder.├── app │ ├── build.gradle │ ├── libs │ │ └── libandroid_tensorflow_inference_java.jar │ └── src │ ├── androidTest │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ ├── imagenet_comp_graph_label_strings.txt │ │ │ └── tensorflow_inception_graph.pb │ │ ├── java │ │ │ └── <main java code> │ │ ├── jniLibs │ │ │ └── armeabi-v7a │ │ │ └── libtensorflow_inference.so │ │ └── res │ └── test ├── assets │ └──<test image> ├── build.gradle ├── gradle ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle
Caffe到tensorflow的转换¶
转换程序代码: caffe-tensorflow
源代码精读¶
Seq2Seq¶
tensorflow/contrib/legacy_seq2seq/python/ops/seq2seq.py
(version 1.11)
tensorflow版本升级之后(r1.3及之后?)把之前的 tf.nn.seq2seq
的代码迁移到了 tf.contrib.legacy_seq2seq
下面,这部分API估计以后会被遗弃,因为已经开发出了新的API放在 tf.contrib.seq2seq
下面,更加灵活。
文件函数结构如下, 共实现了6个seq2seq函数
model_with_buckets()
seq2seq函数
basic_rnn_seq2seq()
:最简单版本,输入和输出都是embedding的形式;最后一步的state vector作为decoder的initial state;encoder和decoder用相同的RNN cell, 但不共享权值参数;
rnn_decoder()
tied_rnn_seq2seq()
:同1,但是encoder和decoder共享权值参数embedding_rnn_seq2seq()
:同1,但输入和输出改为id的形式,函数会在内部创建分别用于encoder和decoder的embedding matrix
embedding_rnn_decoder()
embedding_tied_rnn_seq2seq()
:同2,但输入和输出改为id形式,函数会在内部创建分别用于encoder和decoder的embedding matrixembedding_attention_seq2seq()
:同3,但多了attention机制
embedding_attention_decoder()
attention_decoder()
attention()
one2many_rnn_seq2seq()
loss函数
sequence_loss_by_example()
sequence_loss()
model_with_buckets()
的目的是为了减少计算量和加快模型计算速度,因为这部分代码比较古老——有些地方还在使用static_rnn()这种函数,其实新版的tf中引入dynamic_rnn之后就不需要这么做了。该方法为每个bucket都构造一个模型(这些模型参数共享),然后训练时取相应长度的序列进行。其实这一部分可以参考现在的dynamic_rnn来进行理解,dynamic_rnn是对每个batch的数据将其pad至本batch中长度最大的样本,而bucket则是在数据预处理环节先对数据长度进行聚类操作。
tensorflow/contrib/eager/python/examples/generative_examples
tensorflow/contrib/eager/python/examples/nmt_with_attention
models/research/textsum
nmt
skflow(Scikit Flow)¶
位于
/tensorflow/contrib/learn/python/learn
下面, 模仿Scikit Learn (ML & 数据挖掘工具包)。
代码结构¶
代码分成op、module和model三个部分
定义op 等时使用修饰器加 scope
使用config 文件定义参数
image summary 的输入用placeholder 而非数据,避免在save 的时候不断累加
将网络输入定义在train 的部分而非 init 中,便于reuse
保存log信息(logging)