教程 | 如何利用C++搭建个人专属的TensorFlow
在开始之前,首先看一下最终成型的代码:
- 分支与特征后端(https://github.com/OneRaynyDay/autodiff/tree/eigen)
- 仅支持标量的分支(https://github.com/OneRaynyDay/autodiff/tree/master)
这个项目是我与 Minh Le 一起完成的。
为什么?
如果你修习的是计算机科学(CS)的人的话,你可能听说过这个短语「不要自己动手____」几千次了。它包含了加密、标准库、解析器等等。我想到现在为止,它也应该包含了机器学习库(ML library)。
不管现实是怎么样的,这个震撼的课程都值得我们去学习。人们现在把 TensorFlow 和类似的库当作理所当然了。他们把它看作黑盒子并让它运行起来,但是并没有多少人知道在这背后的运行原理。这只是一个非凸(Non-convex)的优化问题!请停止对代码无意义的胡搞——仅仅只是为了让代码看上去像是正确的。
创一个小群,供大家学习交流聊天
如果有对学C++方面有什么疑惑问题的,或者有什么想说的想聊的大家可以一起交流学习一起进步呀。
也希望大家对学C++能够持之以恒
C++爱好群,
如果你想要学好C++最好加入一个组织,这样大家学习的话就比较方便,还能够共同交流和分享资料,给你推荐一个学习的组织:快乐学习C++组织 可以点击组织二字,可以直达请添加链接描述
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),还有上一个例子用到的链式法则。正如下方所示的形式:
x -> h -> g -> f
作为一个图,我们能够在 f 获得答案,然而,也可以反过来:
dx <- dh <- dg <- df
这样它看起来就像链式法则了!我们需要沿着路径把导数相乘以得到最终的结果。这是一个计算图的例子:
这就将其简化为一个图的遍历问题。有谁察觉到了这就是拓扑排序和深度优先搜索/宽度优先搜索?
没错,为了在两种路径都支持拓扑排序,我们需要包含一套父组一套子组,而汇点是另一个方向的来源。反之亦然。
执行
在开学前,Minh Le 和我开始设计这个项目。我们决定使用特征库后端(Eigen library backend)进行线性代数运算,这个库有一个叫做 MatrixXd 的矩阵类,用在我们的项目中:
class var {// Forward declarationstruct impl;public:
// For initialization of new vars by ptr var(std::shared_ptr<impl>);
var(double);
var(const MatrixXd&);
var(op_type, const std::vector<var>&);
...// Access/Modify the current node value MatrixXd getValue() const;
void setValue(const MatrixXd&);
op_type getOp() const;
void setOp(op_type);// Access internals (no modify) std::vector<var>& getChildren() const;
std::vector<var> getParents() const;
...private:
// PImpl idiom requires forward declaration of the class: std::shared_ptr<impl> pimpl;};struct var::impl{public:
impl(const MatrixXd&);
impl(op_type, const std::vector<var>&);
MatrixXd val;
op_type op;
std::vector<var> children;
std::vector<std::weak_ptr<impl>> parents;};
在这里,我们使用了一个叫「pImpl」的语法,意思是「执行的指针」。它有很多用途,比如接口的解耦实现,以及当在堆栈上有一个本地接口时实例化内存堆上的东西。「pImpl」的一些副作用是微弱的减慢运行时间,但是编译时间缩短了很多。这允许我们通过多个函数调用/返回来保持数据结构的持久性。像这样的树形数据结构应该是持久的。
我们有一些枚举来告诉我们目前正在进行哪些操作:
enum class op_type {
plus,
minus,
multiply,
divide,
exponent,
log,
polynomial,
dot,
...
none // no operators. leaf.};
执行此树的评估的实际类称为 expression:
class expression {public:
expression(var);
...
// Recursively evaluates the tree. double propagate();
...
// Computes the derivative for the entire graph. // Performs a top-down evaluation of the tree. void backpropagate(std::unordered_map<var, double>& leaves);
... private:
var root;};
在反向传播里,我们的代码能做类似以下所示的事情:
backpropagate(node, dprev):
derivative = differentiate(node)*dprev
for child in node.children:
backpropagate(child, derivative)
这几乎就是在做一个深度优先搜索(DFS),你发现了吗?
为什么是 C++?
在实际过程中,C++可能并不适合做这类事情。我们可以在像「Oaml」这样的函数式语言中花费更少的时间开发。现在我明白为什么「Scala」被用于机器学习中,主要就是因为「Spark」。然而,使用 C++有很多好处。
Eigen(库名)
举例来说,我们可以直接使用一个叫「Eigen」的 TensorFlow 的线性代数库。这是一个不假思索就被人用烂了的线性代数库。有一种类似于我们的表达式树的味道,我们构建表达式,它只会在我们真正需要的时候进行评估。然而,使用「Eigen」在编译的时间内就能决定什么时候使用模版,这意味着运行的时间减少了。我对写出「Eigen」的人抱有很大的敬意,因为查看模版的错误几乎让我眼瞎!
他们的代码看起来类似这样的:
Matrix A(...), B(...);
auto lazy_multiply = A.dot(B);
typeid(lazy_multiply).name(); // the class name is something like Dot_Matrix_Matrix.
Matrix(lazy_multiply); // functional-style casting forces evaluation of this matrix.
这个特征库非常的强大,这就是它作为 TensortFlow 主要后端之一的原因,即除了这个慵懒的评估技术之外还有其它的优化。
运算符重载
在 Java 中开发这个库很不错——因为没有 shared_ptrs、unique_ptrs、weak_ptrs;我们得到了一个真实的,有用的图形计算器(GC=Graphing Calculator)。这大大节省了开发时间,更不必说更快的执行速度。然而,Java 不允许操作符重载,因此它们不能这样:
// These 3 lines code up an entire neural network!
var sigm1 = 1 / (1 + exp(-1 dot(X, w1)));
var sigm2 = 1 / (1 + exp(-1 dot(sigm1, w2)));
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 循环真的非常糟糕!)我自己也不是很确定。我完全明白这绝不是一种全面的基准测试,因为它只在特定的情况下应用了单个数据点。然而,这个库的表现并不能代表当前最佳,所以希望各位读者和我们共同完善
转载于:https://blog.51cto.com/14209412/2354021
教程 | 如何利用C++搭建个人专属的TensorFlow相关推荐
- python云服务器搭建教程_Python利用flask搭建一个共享服务器的步骤
这篇文章我们来讲一下在网站建设中,Python利用flask搭建一个共享服务器的步骤.本文对大家进行网站开发设计工作或者学习都有一定帮助,下面让我们进入正文. 零.概述 我利用flask搭建了一个简易 ...
- 树莓派利用FTP搭建文件服务器教程
树莓派利用FTP搭建文件服务器教程 1.安装vsftpd 2.修改配置 /etc/vsftpd.conf 3.创建ftp家庭目录并启动 4.在电脑上下载Xftp软件 在局域网内共享文件除了可以使用SA ...
- win10系统如何配置web服务器,win10系统利用iis搭建web服务器的设置教程
有关win10系统利用iis搭建web服务器的操作方法想必大家有所耳闻.但是能够对win10系统利用iis搭建web服务器进行实际操作的人却不多.其实解决win10系统利用iis搭建web服务器的问题 ...
- 利用WordPress搭建属于自己的网站
怎么用WordPress给自己搭建了一个网站?可能很多人都想拥有属于自己的网站,这篇文章就找你怎么利用WordPress搭建属于自己的网站.如果你也正好有搭建个人网站的想法,那么本文会给你一个参考,我 ...
- 利用自己的电脑设置web服务器建网站_win7系统篇,win7系统利用iis搭建web服务器实现信息浏览资源共享的操作方法...
很多小伙伴都遇到过对win7系统利用iis搭建web服务器实现信息浏览资源共享进行设置的困惑吧,一些朋友看过网上对win7系统利用iis搭建web服务器实现信息浏览资源共享设置的零散处理方法,并没有完 ...
- 利用 Docker 搭建单机的 Cloudera CDH 以及使用实践
利用 Docker 搭建单机的 Cloudera CDH 以及使用实践 想用 CDH 大礼包,于是先在 Mac 上和 Centos7.4 上分别搞个了单机的测试用.其实操作的流和使用到的命令差不多就一 ...
- 利用Octopress搭建一个Github博客
小引 Octopress是利用Jekyll博客引擎开发的一个博客系统,生成的静态页面能够很好的在github page上展现.号称是hacker专属的一个博客系统(A blogging framewo ...
- 如何利用wordpress搭建自己独立的博客(个人网站)
原址:http://blog.csdn.net/edroid1530/article/details/51136896 如何利用wordpress搭建自己独立的博客(个人网站) 搭建博客的方式多种样, ...
- 搭建一个专属于两个人的爱情网站,记录生活中的点点滴滴
搭建一个专属于两个人的爱情网站,记录生活中的点点滴滴 爱情是世界上最为令人着迷的情感,它可以让两个原本毫无关系的人变成最密不可分的伴侣.它可以是山盟海誓也可以是柴米油盐,有些人恋爱喜欢拍照,吃了什么, ...
最新文章
- Pycharm快捷键及一些常用设置
- c#_List<T>(IEnumerable<T>)
- 共轭梯度下降法matlab,用matlab实现最速下降法,牛顿法和共轭梯度法求解实例
- Requests方法 -- 关联用例执行
- 怎样从0开始搭建一个测试框架_1
- 大厂的 404 页面都长啥样?看到最后一个,我笑了...
- 数组和集合的区别?你还知道这些吗?
- Debian Linux 的 vim 如何使用系统剪贴板
- 0-安装Vagrant和使用
- UC浏览器怎么删除收藏历史?UC浏览器删除收藏历史的操作方法
- springmvc中Date类型转换
- kml文件转成cvs_KMZ KML与SHP文件互相转换
- 使用Docker部署mongo后 使用Robo 3T、Studio 3T( MongoChef )在 create databse 创建数据库时的掉坑笔记
- 解决margin坍塌
- Unity AB加载预设体导致Rotation发生变化
- .lnk 文件恢复默认打开方式
- mac 百度输入法如何切换成五笔,如何切换回拼音模式
- node 文件重命名
- 数位DP入门笔记(1)HUD-2089
- 数仓建模—数仓架构发展史(02)
热门文章
- android资产目录,android – 从非目录设备中的资产文件夹复制数据库
- django oracle 性能,4.利用Django在前端展示Oracle 状态趋势
- mysql修改客户端编码命令_mysql命令行修改字符编码
- mysql 使用储存过程_为什么使用mysql储存过程?mysql储存过程简介
- js判断ipad还是安卓_JS判断客户端是否是iOS或者Android
- 变频器输出功率_100米的深井泵,如何接变频器,怎样控制
- 浏览器里面看到的表单数据映射到python_python爬虫入门01:教你在 Chrome 浏览器轻松抓包...
- numpy 最大值_第 85 天:NumPy 统计函数
- mastercam加工报表生成_2020北京加工中心编程培训工厂教学行业
- 磊科路由虚拟服务器设置,磊科路由器虚拟转发服务设置的方法