如何利用C++搭建个人专属的TensorFlow

在开始之前,首先看一下最终成型的代码:

1. 分支与特征后端(https://github.com/OneRaynyDay/autodiff/tree/eigen)

2. 仅支持标量的分支(https://github.com/OneRaynyDay/autodiff/tree/master)

这个项目是我与 Minh Le 一起完成的。

为什么?

如果你修习的是计算机科学(CS)的人的话,你可能听说过这个短语「不要自己动手____」几千次了。它包含了加密、标准库、解析器等等。我想到现在为止,它也应该包含了机器学习库(ML library)。

不管现实是怎么样的,这个震撼的课程都值得我们去学习。人们现在把 TensorFlow 和类似的库当作理所当然了。他们把它看作黑盒子并让它运行起来,但是并没有多少人知道在这背后的运行原理。这只是一个非凸(Non-convex)的优化问题!请停止对代码无意义的胡搞——仅仅只是为了让代码看上去像是正确的。

TensorFlow

在 TensorFlow 的代码里,有一个重要的组件,允许你将计算串在一起,形成一个称为「计算图」的东西。这个计算图是一个有向图 G=(V,E),其中在某些节点处 u1,u2,…,un,v∈V,和 e1,e2,…,en∈E,ei=(ui,v)。我们知道,存在某种计算图将 u1,…,un 映射到 vv。

举个例子,如果我们有 x + y = z,那么 (x,z),(y,z)∈E。

这对于评估算术表达式非常有用,我们能够在计算图的汇点下找到结果。汇点是类似 v∈V,∄e=(v,u) 这样的顶点。从另一方面来说,这些顶点从自身到其他顶点并没有定向边界。同样的,输入源是 v∈V,∄e=(u,v)。

对于我们来说,我们总是把值放在输入源上,而值也将传播到汇点上。

反向模式求微分

如果你觉得我的解释不正确,可以参考下这些幻灯片的说明。

微分是 Tensorflow 中许多模型的核心需求,因为我们需要它来运行梯度下降。每一个从高中毕业的人都应该知道微分的意思。如果是基于基础函数组成的复杂函数,则只需要求出函数的导数,然后应用链式法则。

超级简洁的概述

如果我们有一个像这样的函数:

对 x 求导:

对 y 求导:

其它的例子:

其导数是:

所以其梯度是:

链式法则,例如应用于 f(g(h(x))):

在 5 分钟内倒转模式

所以现在请记住我们运行计算图时用的是有向无环结构(DAG/Directed Acyclic Graph),还有上一个例子用到的链式法则。正如下方所示的形式:


  1. x -> h -> g -> f 

作为一个图,我们能够在 f 获得答案,然而,也可以反过来:


  1. dx <- dh <- dg <- df 

这样它看起来就像链式法则了!我们需要沿着路径把导数相乘以得到最终的结果。这是一个计算图的例子:

这就将其简化为一个图的遍历问题。有谁察觉到了这就是拓扑排序和深度优先搜索/宽度优先搜索?

没错,为了在两种路径都支持拓扑排序,我们需要包含一套父组一套子组,而汇点是另一个方向的来源。反之亦然。

执行

在开学前,Minh Le 和我开始设计这个项目。我们决定使用特征库后端(Eigen library backend)进行线性代数运算,这个库有一个叫做 MatrixXd 的矩阵类,用在我们的项目中:


  1. class var {// Forward declarationstruct impl;public: 
  2.     // For initialization of new vars by ptr    var(std::shared_ptr<impl>); 
  3.  
  4.     var(double); 
  5.     var(const MatrixXd&); 
  6.     var(op_type, const std::vector<var>&);     
  7.     ... 
  8.      
  9.     // Access/Modify the current node value    MatrixXd getValue() const; 
  10.     void setValue(const MatrixXd&); 
  11.     op_type getOp() const; 
  12.     void setOp(op_type); 
  13.      
  14.     // Access internals (no modify)    std::vector<var>& getChildren() const; 
  15.     std::vector<var> getParents() const; 
  16.     ...private:  
  17.     // PImpl idiom requires forward declaration of the class:    std::shared_ptr<impl> pimpl;};struct var::impl{public: 
  18.     impl(const MatrixXd&); 
  19.     impl(op_type, const std::vector<var>&); 
  20.     MatrixXd val; 
  21.     op_type op;  
  22.     std::vector<var> children; 
  23.     std::vector<std::weak_ptr<impl>> parents;};  

在这里,我们使用了一个叫「pImpl」的语法,意思是「执行的指针」。它有很多用途,比如接口的解耦实现,以及当在堆栈上有一个本地接口时实例化内存堆上的东西。「pImpl」的一些副作用是微弱的减慢运行时间,但是编译时间缩短了很多。这允许我们通过多个函数调用/返回来保持数据结构的持久性。像这样的树形数据结构应该是持久的。

我们有一些枚举来告诉我们目前正在进行哪些操作:


  1. enum class op_type { 
  2.     plus, 
  3.     minus, 
  4.     multiply, 
  5.     divide, 
  6.     exponent, 
  7.     log, 
  8.     polynomial, 
  9.     dot, 
  10.     ... 
  11.     none // no operators. leaf.};  

执行此树的评估的实际类称为 expression:


  1. class expression {public: 
  2.     expression(var); 
  3.     ... 
  4.     // Recursively evaluates the tree.    double propagate(); 
  5.     ... 
  6.     // Computes the derivative for the entire graph.    // Performs a top-down evaluation of the tree.    void backpropagate(std::unordered_map<var, double>& leaves); 
  7.     ...    private: 
  8.     var root;};  

在反向传播里,我们的代码能做类似以下所示的事情:


  1. backpropagate(node, dprev): 
  2.     derivative = differentiate(node)*dprev 
  3.     for child in node.children: 
  4.         backpropagate(child, derivative)  

这几乎就是在做一个深度优先搜索(DFS),你发现了吗?

为什么是 C++?

在实际过程中,C++可能并不适合做这类事情。我们可以在像「Oaml」这样的函数式语言中花费更少的时间开发。现在我明白为什么「Scala」被用于机器学习中,主要就是因为「Spark」。然而,使用 C++有很多好处。

Eigen(库名)

举例来说,我们可以直接使用一个叫「Eigen」的 TensorFlow 的线性代数库。这是一个不假思索就被人用烂了的线性代数库。有一种类似于我们的表达式树的味道,我们构建表达式,它只会在我们真正需要的时候进行评估。然而,使用「Eigen」在编译的时间内就能决定什么时候使用模版,这意味着运行的时间减少了。我对写出「Eigen」的人抱有很大的敬意,因为查看模版的错误几乎让我眼瞎!

他们的代码看起来类似这样的:


  1. Matrix A(...), B(...); 
  2. auto lazy_multiply = A.dot(B); 
  3. typeid(lazy_multiply).name(); // the class name is something like Dot_Matrix_Matrix. 
  4. Matrix(lazy_multiply); // functional-style casting forces evaluation of this matrix.  

这个特征库非常的强大,这就是它作为 TensortFlow 主要后端之一的原因,即除了这个慵懒的评估技术之外还有其它的优化。

运算符重载

在 Java 中开发这个库很不错——因为没有 shared_ptrs、unique_ptrs、weak_ptrs;我们得到了一个真实的,有用的图形计算器(GC=Graphing Calculator)。这大大节省了开发时间,更不必说更快的执行速度。然而,Java 不允许操作符重载,因此它们不能这样:


  1. // These 3 lines code up an entire neural network! 
  2. var sigm1 = 1 / (1 + exp(-1 * dot(X, w1))); 
  3. var sigm2 = 1 / (1 + exp(-1 * dot(sigm1, w2))); 
  4. var loss = sum(-1 * (y * log(sigm2) + (1-y) * log(1-sigm2)));  

顺便说一下,上面是实际使用的代码。是不是非常的漂亮?我想说的是这甚至比 TensorFlow 里的 Python 封装还更优美!我只是想表明,它们也是矩阵。

在 Java 中,有一连串的 add(), divide() 等等是非常难看的。更重要的是,这将让用户更多的关注在「PEMDAS」上,而 C++的操作符则有非常好的表现。

特征,而不是一连串的故障

在这个库中,可以确定的是,TensorFlow 没有定义清晰的 API,或者有但我不知道。例如,如果我们只想训练一个特定子集的权重,我们可以只对我们感兴趣的特定来源做反向传播。这对于卷积神经网络的迁移学习非常有用,因为很多时候,像 VGG19 这样的大型网络可以被截断,然后附加一些额外的层,这些层的权重使用新领域的样本来训练。

基准

在 Python 的 TensorFlow 库中,对虹膜数据集进行 10000 个「Epochs」的训练以进行分类,并使用相同的超参数,我们有:

  1. TensorFlow 的神经网络: 23812.5 ms
  2. 「Scikit」的神经网络:22412.2 ms
  3. 「Autodiff」的神经网络,迭代,优化:25397.2 ms
  4. 「Autodiff」的神经网络,迭代,无优化:29052.4 ms
  5. 「Autodiff」的神经网络,带有递归,无优化:28121.5 ms

令人惊讶的是,Scikit 是所有这些中最快的。这可能是因为我们没有做庞大的矩阵乘法。也可能是 TensorFlow 需要额外的编译步骤,如变量初始化等等。或者,也许我们不得不在 python 中运行循环,而不是在 C 中(Python 循环真的非常糟糕!)我自己也不是很确定。我完全明白这绝不是一种全面的基准测试,因为它只在特定的情况下应用了单个数据点。然而,这个库的表现并不能代表当前最佳,所以希望各位读者和我们共同完善。 

原文发布时间为:2017-10-30

本文作者:刘晓坤

本文来自合作伙伴“51CTO”,了解相关信息可以关注。

时间: 2024-12-09 07:35:15

如何利用C++搭建个人专属的TensorFlow的相关文章

做淘客更踏实利用phurl搭建自己的短网址网站

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 说一下如何搭一个独立的短网址网站,好处,1.安全:避免被随意修改ID,2.稳定:只要你自己不删除,连接一直有效,3.用途广:新浪微博也可以发链接,想发那发那,群发还可以避免网站降权,4.便宜,我看到很多朋友用的都是国外空间,一般都可以搭建多个网站,只需投入一个域名就可以了,或者使用二级域名投入更少(如果使用二级域名第3条无效,不要去做). 之

ubuntu 利用virtualenv 搭建多python环境出错

问题描述 ubuntu 利用virtualenv 搭建多python环境出错 最近在网上看到一个博客http://blog.chinaunix.net/uid-15174104-id-4020426.html,貌似挺不错的,就自己试试搭建一下比较干净的ubuntu多python环境,结果刚开始就崩了,查了很多的资料还是没有解决,这里求大神解答 我在ubuntu下装了一个ubuntu-12.04.3,打算试试您的python环境搭建,但是中间遇到了点问题 我手动安装的python3.3.3,官网下

linux 如何利用ISO搭建yum仓库

linux 如何利用ISO搭建yum仓库 作者 digoal 日期 2016-11-12 标签 Linux , iso , yum , repo 背景 在使用Linux时,很多包有依赖关系,所以很多LINUX的发行版本都有自己的仓库管理软件,例如suse , debian, ubuntu , centos , rhel. 等等发行版本,各自有各自的软件仓库管理方法. 以CentOS为例,红帽派系的基本上都使用的是yum仓库管理手段. 使用仓库管理可以方便的安装rpm包,不需要自己挨个去解决依赖问

《循序渐进学Docker》——2.2 利用Docker搭建个人博客

2.2 利用Docker搭建个人博客 WordPress是一款功能强大的个人博客系统.使用者众多,社区非常活跃,有丰富的插件模板资源.使用WordPress可以快速搭建独立的博客网站. 2.2.1 传统的安装方法 按照传统的安装方法,参考官方的安装文档(http://codex.wordpress.org/zh-cn:安装_WordPress) ,安装步骤如图2-5所示. WordPress运行环境需要如下软件的支持: PHP 5.6或更新版本. MySQL5.6或更新版本. Apache和mo

利用docker搭建LAMP运行环境教程详解_docker

LAMP介绍 LAMP 指的 Linux(操作系统).ApacheHTTP 服务器,MySQL(有时也指MariaDB,数据库软件) 和 PHP(有时也是指 Perl 或 Python) 的第一个字母,一般用来建立 web 服务器. 虽然这些开放源代码程序本身并不是专门设计成同另几个程序一起工作的,但由于它们的免费和开源,这个组合开始流行(大多数Linux发行版本***了这些软件).当一起使用的时候,它们表现的像一个具有活力的解决方案包. 下面介绍如何使用docker来搭建一个包含lamp组件的

网盘关闭不用怕:利用Docker和OSS轻松搭建ownCloud专属网盘

文件共享是团队协作的刚需,面对说停就停的网盘服务,很多同学除了心塞已经开始盘算搭建自己的网盘应用了. ownCloud 是一个开源免费的存储管理工具,它能帮你快速架设一套专属的网盘服务,可以像 Dropbox 那样实现文件跨平台同步.共享.版本控制.团队协作等等. 然而在VPS上搭建ownCloud是一件费时费力的工作,除此之外更需要考虑数据的可靠性.备份等工作. 本文将利用阿里云容器服务在几分钟内轻松搭建一个基于Docker的ownCloud专属网盘,并使用阿里云提供的OSS(Object S

专家支招:如何利用Plesk搭建完美全能开发环境

在如此快节奏发展的时代,无论是站长.网页设计托管还是企业网站管理员都不愿意将大量时间精力贡献在搭建服务器环境上,过程复杂繁琐不说还往往暗藏漏洞.如果有一款管理型服务器,不仅自带全能开发环境,而且配以操纵自如的可视化操作界面,不就能跃过技术门槛,快速高效开展核心业务了? 其实,只要在服务器上安装一个Plesk操作面板,所有开发环境便可手到擒来.Plesk不仅包括多个版本的PHP,而且可以自如管理 Ruby,Python,Node.js,Java, Nigix,Git等等.最新推出的Plesk On

利用阿里云容器服务打通TensorFlow持续训练链路

本系列将利用Docker和阿里云容器服务,帮助您上手TensorFlow的机器学习方案 第一篇:打造TensorFlow的实验环境 第二篇:轻松搭建TensorFlow Serving集群 第三篇:打通TensorFlow持续训练链路 第四篇:利用Neural Style的TensorFlow实现,像梵高一样作画 第五篇:轻松搭建分布式TensorFlow训练集群(上) 本文是该系列中的第三篇文章, 将为您介绍如何利用阿里云的服务快速搭建TensorFlow从训练到服务的交付平台. 随着goog

CSDN学生大本营满百天 利用SNS搭建4培训基地

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 最新消息,CSDN学生大本营(http://student.csdn.net)作为在线IT知识及经验的传播通道已经上线百天.利用SNS汇集人员快的特性,CSDN学生大本营得到了快速发展,目前,学生大本营用户已经近两万名,形成了90多位来自IT业界经验丰富的专家组成的老师队伍,推出13个技术大类课堂,建成了4个培训基地. CSDN学生大本营 C