Tensorflow源码解读

原创文章,转载请注明: 转载自慢慢的回味

本文链接地址: Tensorflow源码解读

TensorFlow是一个灵活的端到端的机器学习框架。通过底层的代码调试更能够了解它的原理。

第一部分 Tensorflow c lib的编译

Tensorflow的下载和编译 本篇介绍编译的过程及注意事项。

第二部分 Tensorflow Eclipse环境的搭建

由于我们是要研究tensorflow的实现,所以用一个简单的c程序来调用tensorflow c library来用gdb调试。
正常的使用python写程序,然后由python调用so文件,但那样没办法调试到tensorflow内部。

TensorFlow工程创建及设置
基于tensorflow c lib调试的主程序
使用VSCode 调试tensorflow c lib的简单方法

第三部分 Tensorflow 源码调试

本部分介绍Tensorflow的实现机制。

  • 启动后,ops和op kernels的定义由静态变量声明
  • 然后根据用户程序生成计算图,查找ops的声明调用ops的factory函数注册ops并付给图中的node
  • 对生成的图进行优化剪裁
  • 创建executor,把root node喂给executor开始计算图的执行,完成后返回给主线程结束程序
TensorFlow op和op kernel的注册 TensorFlow so文件加载时完成TensorFlow op和op kernel的注册。另一篇TF Operation的注册
TensorFlow图的构建 本文就来分析tensorflow图的构建,以及运行图的设备和线程池的创建。TensorFlow使用数据流图将计算表示为独立的指令之间的依赖关系,数据流是一种用于并行计算的常用编程模型。在数据流图中,节点表示计算单元,边缘表示计算使用或产生的数据。
TensorFlow Session的创建 本文介绍TensorFlow Session的创建。TensorFlow使用Session 类来表示客户端程序(通常用Python 程序,但也提供了其他语言的类似接口,这儿就是用C接口)与 C++ 运行时之间的连接。Session 对象使我们能够访问本地机器中的设备和使用分布式 TensorFlow 运行时的远程设备。它还可缓存关于Graph 的信息,使您能够多次高效地运行同一计算。Session接受Graph参数和Options选项参数,Options参数可以指定使用的设备等信息。
TensorFlow Session的Setup TensorFlow Session的Setup完成整个Session的创建,设置输入数据类型(feeds)和输出数据类型(fetches)。然后利用图Graph创建基于Session的基本图,开启线程器(Exectors)等待Session开始。
TensorFlow Executor的创建 Session中的Executeors是一个线程池,用来执行每个节点Node的计算。在上一篇Session的setup中,其中调用了GetOrCreateExecutors类创建Executeors。direct_session.cc中的GetOrCreateExecutors方法用于获取设置callable_options的inputs,fetches,target,然后继续调用CreateExecutors方法创建executors。
tf-operation的创建 以AddNOp为例说明Operation怎样从ops的定义创建具体的kernel实例。
TensorFlow计算图的创建 Tensorflow机器学习任务的核心就是根据用户定义的图graph模型以及参数求解方式进行抽象之后,生成一个由节点和有向边组成,可以确定一个唯一的计算逻辑用有向无环图,称之为计算图。它定义了数据的流转方式,数据的计算方式,以及各种计算之间的相互依赖关系等。节点包括计算节点(Operation)、存储节点(Variable)和数据节点(Placeholder)3类,用于计算数据和存储数据。有向边表示数据的流转方式和依赖。
TensorFlow计算图的优化 Tensorflow计算图的优化相当重要,通过对其优化可以显著降低无用代码对计算资源的消耗,尤其在深度学习时,每一此迭代时间的缩短可以大大加速整个学习结果的求解,降低的资源消耗又可以容纳更多的输入并行计算。常见的优化有常量折叠,公共表达式折叠,内联函数展开,算数优化,修剪不可达节点,调试代码去除,自动并行计算,循环优化,内存优化等。
TF 计算图的执行 TF计算图优化完成后,在Session开始执行后就轮到TF 计算图的执行了。Tensorflow会根据计算图的节点信息,首先找到一个没有输入的节点作为根节点,创建一个task交给线程池取执行。每个节点完成后会根据Edge通知下游节点计算,直到所有节点完成计算,然后输出结果。
Tensorflow C API实现卷积计算 一个基于C API实现的进行卷积计算的示例。
Tensorflow 算法优化器验证 通过一个很简单的程序来验算法优化器的功能。如下程序,通过原图:输入数据->取负数->取负数->取倒数->取倒数->得到结果,经过优化计算图直接变成:输入数据->得到结果,计算节点数目大大减小了。
Tensorflow 函数式编程的测试 本文通过一个简单的程序来验证Tensorflow能够进行函数式编程的底层支持。从此也说明,Tensorflow在2.0发布之前的1.15版本底层已经支持函数式编程,进而能够实现动态图计算。

第四部分 Tensorflow常用实例

本部分介绍Tensorflow一些常用的实例。

Tensorflow Embedding原理 Embedding(嵌入)指的是把低维的流形嵌入到高维空间中。举个简单的例子,三维空间的球体(地球)是一个二维流形嵌入在三维空间(欧几里得空间),即地球上的任意一个点只需一个二维的经纬度就可以表达,但三维空间中用x,y,z。深度学习领域假设“自然的原始数据是低维的流形嵌入到原始数据所在的高维空间中”。所以,深度学习的过程就是把高维原始数据(图像,句子)再回映射到低维流形中,从而是数据变得可分,而这个映射就叫嵌入(Embedding)。比如文档Embedding,就是把每篇文档所组成的单词映射到一个低维的表征向量,使得每篇文档可以用一个表征向量来表示,即Embedding就是从原始数据提取出来的Feature,也就是通过神经网络映射之后的低维向量。
Tensorflow Pooling池化原理 池化(Pooling):也称为欠采样或下采样。主要用于特征降维,在保持旋转、平移、伸缩等不变性的前提下,压缩数据和参数的数量,减小过拟合,同时提高模型的容错性。常用的有按均值池化(mean-pooling):更大地保留图像背景信息,按最大值池化(max-pooling):更多的保留纹理信息。
Tensorflow 文本分类实例
Tensorflow Conv2D和MaxPool2D原理 卷积神经网络(CNN)是指在所有网络中,至少优一层使用了卷积运算运算的神经网络,因此命名为卷积神经网络。那么什么是卷积呢?如果需要卷积一个二位图片,首先定义一个卷积核(kernel),即权重矩阵,它能表面每一次卷积那个方向的值更重要,然后逐步在二维输入数据上“扫描卷积”。当卷积核“滑动”的同时进行卷积:计算权重矩阵和扫描所得的数据矩阵的乘积,求和后得到一个像素输出。
Tensorflow 图像CNN分类解析 自从Yann Lecun最早将CNN用于手写数字识别后,卷积神经网络在多个方向持续火爆:如语音识别、人脸识别、通用物体识别等。与普通神经网络的最大区别为:卷积神经网络包含了由卷积层和池化层构成的特征抽取器。卷积层由卷积核做卷积运算替代全连接神经网络的矩阵计算。卷积核表示一组共享的权值,位于不同地方的权值可以模拟人眼对图像识别的局部感受野:一般认为人对外界的认知是从局部到全局的,而图像的空间联系也是局部的像素联系较为紧密,而距离较远的像素相关性则较弱。共享的权值还可以大大降低权值参数量。所以卷积核的局部感受野可以解决全连接计算将图像展开为向量丢失的空间信息;共享权值减少的参数可以提高训练效率和避免网络过拟合。
Tensorflow LSTM原理 LSTM是RNN的一种cell实现。那么什么是RNN呢?RNN是一种特殊的神经网络结构, 它是根据“人的认知是基于过往的经验和记忆”这一观点而提出的,它使网络对前面的内容的一种“记忆”功能。隐藏层中,一个序列当前的输出与前面的输出也有关,具体的表现形式为网络会对前面的信息进行记忆并应用于当前输出的计算中,即隐藏层之间的节点不再无连接而是有连接的,并且隐藏层的输入不仅包括输入层的输出还包括上一时刻隐藏层的输出,所有前面的结果可以影响后面的输出。所以它广泛运用于文本生成,机器翻译,机器写小说,语音识别等。

本作品采用知识共享署名 4.0 国际许可协议进行许可。

Tensorflow的下载和编译

原创文章,转载请注明: 转载自慢慢的回味

本文链接地址: Tensorflow的下载和编译


回目录

从github获取源代码

TensorFlow是一个灵活的端到端的机器学习框架。这儿使用1.15版本,编译完后大概6GB,Debug的时候GDB加载到内存大概12.8GB,而2.4版本编译完后有12GB,加载到内存需要19GB。
1.15版本和2.4版本的核心实现原理是一致的。这儿使用Ubuntu 20.04版本,GDB 9.1版本。

git clone https://github.com/tensorflow/tensorflow.git
git checkout -b r1.15 remotes/origin/r1.15
cd tensorflow
按照前置需要软件包
sudo apt install python3-dev python3-pip
pip install -U --user pip six 'numpy<1.19.0' wheel setuptools mock 'future>=0.17.1' 'gast==0.3.3' typing_extensions
pip install -U --user keras_applications --no-deps
pip install -U --user keras_preprocessing --no-deps
配置TensorFlow

配置的时候能不编译的组件都选择N取消,调试核心框架不需要那些。

./configure
安装编译工具:Bazel

参考https://docs.bazel.build/versions/0.26.0/install-ubuntu.html进行安装。也可点击bazel-0.26.1下载。

sudo chmod +x bazel-0.26.1-linux-x86_64 
./bazel-0.26.1-linux-x86_64 
sudo ln -s bazel-0.26.1-linux-x86_64 /usr/bin/bazel
编译TensorFlow

Build TensorFlow的debug版本,即可以用GDB Debug的版本:
Build的时候加上”–copt -g“:
通过查看任何一个.o文件来确认是否带有debug信息。例如gdb conv_ops_3d.pic.o验证,只要不出现No symbols则说明debug信息有。

[root@localhost conv_ops]# gdb conv_ops_3d.pic.o
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/.cache/bazel/_bazel_root/e88dc1dcc3c90dfdeee7304faf39c313/execroot/org_tensorflow/bazel-out/k8-opt/bin/tensorflow/core/kernels/_objs/conv_ops/conv_ops_3d.pic.o...done.
(gdb)

编译之前需要应用一个patch避免编译错误:

@@ -0,0 +1,78 @@
From 57586a1ca7f17b1916aed3dea4ff8de872dbf853 Mon Sep 17 00:00:00 2001
From: Benjamin Peterson <benjamin@dropbox.com>
Date: Fri, 3 May 2019 08:11:00 -0700
Subject: [PATCH] Rename gettid() functions.
 
glibc 2.30 will declare its own gettid; see https://sourceware.org/git/?p=glibc.git;a=commit;h=1d0fc213824eaa2a8f8c4385daaa698ee8fb7c92. Rename the grpc versions to avoid naming conflicts.
---
 src/core/lib/gpr/log_linux.cc          | 6 ++----
 src/core/lib/gpr/log_posix.cc          | 4 ++--
 src/core/lib/iomgr/ev_epollex_linux.cc | 4 ++--
 3 files changed, 6 insertions(+), 8 deletions(-)
 
diff --git a/src/core/lib/gpr/log_linux.cc b/src/core/lib/gpr/log_linux.cc
index 81026e5689b..8b597b4cf2f 100644
--- a/src/core/lib/gpr/log_linux.cc
+++ b/src/core/lib/gpr/log_linux.cc
@@ -40,7 +40,7 @@
 #include <time.h>
 #include <unistd.h>
 
-static long gettid(void) { return syscall(__NR_gettid); }
+static long sys_gettid(void) { return syscall(__NR_gettid); }
 
 void gpr_log(const char* file, int line, gpr_log_severity severity,
              const char* format, ...) {
@@ -70,7 +70,7 @@ void gpr_default_log(gpr_log_func_args* args) {
   gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME);
   struct tm tm;
   static __thread long tid = 0;
-  if (tid == 0) tid = gettid();
+  if (tid == 0) tid = sys_gettid();
 
   timer = static_cast<time_t>(now.tv_sec);
   final_slash = strrchr(args->file, '/');
diff --git a/src/core/lib/gpr/log_posix.cc b/src/core/lib/gpr/log_posix.cc
index b6edc14ab6b..2f7c6ce3760 100644
--- a/src/core/lib/gpr/log_posix.cc
+++ b/src/core/lib/gpr/log_posix.cc
@@ -31,7 +31,7 @@
 #include <string.h>
 #include <time.h>
 
-static intptr_t gettid(void) { return (intptr_t)pthread_self(); }
+static intptr_t sys_gettid(void) { return (intptr_t)pthread_self(); }
 
 void gpr_log(const char* file, int line, gpr_log_severity severity,
              const char* format, ...) {
@@ -86,7 +86,7 @@ void gpr_default_log(gpr_log_func_args* args) {
   char* prefix;
   gpr_asprintf(&prefix, "%s%s.%09d %7" PRIdPTR " %s:%d]",
                gpr_log_severity_string(args->severity), time_buffer,
-               (int)(now.tv_nsec), gettid(), display_file, args->line);
+               (int)(now.tv_nsec), sys_gettid(), display_file, args->line);
 
   fprintf(stderr, "%-70s %s\n", prefix, args->message);
   gpr_free(prefix);
diff --git a/src/core/lib/iomgr/ev_epollex_linux.cc b/src/core/lib/iomgr/ev_epollex_linux.cc
index c2d80c08ddb..4a83cb6c215 100644
--- a/src/core/lib/iomgr/ev_epollex_linux.cc
+++ b/src/core/lib/iomgr/ev_epollex_linux.cc
@@ -1077,7 +1077,7 @@ static void end_worker(grpc_pollset* pollset, grpc_pollset_worker* worker,
 }
 
 #ifndef NDEBUG
-static long gettid(void) { return syscall(__NR_gettid); }
+static long sys_gettid(void) { return syscall(__NR_gettid); }
 #endif
 
 /* pollset->mu lock must be held by the caller before calling this.
@@ -1097,7 +1097,7 @@ static grpc_error* pollset_work(grpc_pollset* pollset,
 #define WORKER_PTR (&worker)
 #endif
 #ifndef NDEBUG
-  WORKER_PTR->originator = gettid();
+  WORKER_PTR->originator = sys_gettid();
 #endif
   if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO,

使用如下的命令进行编译:

bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/tools/lib_package:libtensorflow --verbose_failures
 
#Other libraries may need for debugging.
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/cc:cc_ops --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/c:c_test_util --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/c:c_api --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/cc:const_op --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/cc:array_ops --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/cc:ops --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/cc:match_ops --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/cc:math_ops --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/cc:scope --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/core:test --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/core:testlib --verbose_failures
bazel build -s --config=opt  --copt -g --config=noaws --config=nogcp --config=nohdfs --config=noignite --config=nokafka --config=nonccl //tensorflow/core/kernels:ops_testutil --verbose_failures

关于debug level的解释:

-glevel
Request debugging information and also use level to specify how much information. The default level is 2.
Level 0 produces no debug information at all. Thus, -g0 negates -g.
Level 1 produces minimal information, enough for making backtraces in parts of the program that you don't plan to debug. This includes descriptions of functions and external variables, but no information about local variables and no line numbers.
Level 3 includes extra information, such as all the macro definitions present in the program. Some debuggers support macro expansion when you use -g3.

本作品采用知识共享署名 4.0 国际许可协议进行许可。

CycleGAN模型原理

原创文章,转载请注明: 转载自慢慢的回味

本文链接地址: CycleGAN模型原理

CycleGAN模型原理

前面我们了解了好几种GAN,它们大致可分为:
随机生成模型的GAN,包括GAN,DCGAN,WGAN,WGAN-GP等;
带条件生成模型的GAN,包括CGAN,InfoGAN,ACGAN等。
它们都是监督学习模型,即生成网络都有一个目标样本集。

除了监督学习模型,还有一类非监督学习模型,比如CycleGAN。
CycleGAN如下图所示,它能在油画到相片互相生成;马到斑马互相生成;夏天到冬天季节相互变化。它们都不是要把当前域的样本拟合到另一个域。
它们只是把当前域X的某种公共特征(着色填充)变成了另一个域Y的公共特征,且保持当前图片的主要特征(线条轮廓)不变。
即:
1 X域所有图片的共性:油画 -> Y域所有图片的共性:真实照片,当前主要特征画内容的线条保持不变;
2 X域所有图片的共性:马的纹理 -> Y域所有图片的共性:斑马纹理,当前马的轮廓不会变;
3 X域所有图片的共性:夏天风景 -> Y域所有图片的共性:冬天风景,当前的河流,树木轮廓都不会变。
继续阅读“CycleGAN模型原理”本作品采用知识共享署名 4.0 国际许可协议进行许可。

WGAN-GP与WGAN的区别

原创文章,转载请注明: 转载自慢慢的回味

本文链接地址: WGAN-GP与WGAN的区别

WGAN-GP与WGAN的区别

相比较WGAN,WGAN-GP不再使用clip野蛮的剪裁鉴别网络的梯度值,而是使用梯度惩罚来使梯度更新平滑,即满足1-lipschitz条件,解决了训练梯度消失梯度爆炸的问题。
WGAN视频讲解参考

1 使用随机方式把真实图片和伪造图片混合在一起。

class RandomWeightedAverage(_Merge):
    """Provides a (random) weighted average between real and generated image samples"""
    def _merge_function(self, inputs):
        alpha = K.random_uniform((32, 1, 1, 1))
        return (alpha * inputs[0]) + ((1 - alpha) * inputs[1])

继续阅读“WGAN-GP与WGAN的区别”本作品采用知识共享署名 4.0 国际许可协议进行许可。

WGAN与DCGAN的区别

原创文章,转载请注明: 转载自慢慢的回味

本文链接地址: WGAN与DCGAN的区别

WGAN与DCGAN的区别

1 它们的主要区别在于损失函数不一样,DCGAN使用的是二位交叉熵(binary_crossentropy),WGAN使用的是推土机距离(wasserstein_loss),即目标值与预测值乘积的均值,所以鉴别网络末端不再使用激活函数sigmoid,而是直接输出全连接网络的值。

    def wasserstein_loss(self, y_true, y_pred):
        return K.mean(y_true * y_pred)

这儿需要注意的是:
对于真实的图片,y_true即目标值是-1,参考如下代码。而预测值是0-1,所有预测为真时(鉴别成功),值为-1×1=-1,为伪造的图片时(鉴别失败),值为-1×0=0,即鉴别失败时损失大(0>-1);
对于伪造的图片,y_true即目标值是1,参考如下代码。而预测值是0-1,所有预测为真时(鉴别失败),值为1×1=1,为伪造的图片时(鉴别成功),值为1×0=0,即鉴别失败时损失大(1>0);

        # Adversarial ground truths
        valid = -np.ones((batch_size, 1))
        fake = np.ones((batch_size, 1))

继续阅读“WGAN与DCGAN的区别”本作品采用知识共享署名 4.0 国际许可协议进行许可。