目录
2.6 tensorflow和pytorch 和 caffe的比较
0.写在前面
CS231n是非常好的计算机视觉课程,官方也提供的课程笔记,但是从第p19课时起,CS231n就不提供官方笔记了,所以从这一课开始,博主开始记录课程笔记,希望可以帮到大家。
1.CPU VS GPU
1.1GPU 与CPU的比较
如图是目前顶级GPU和CPU的区别,GPU和CPU都是通用的计算机器,它们可以执行程序和一些指令。但它们在性质上是非常不同的。
CPU只有为数不多的核数,利用多线程技术,可以在硬件上同时运行多个线程,CPU可以同时运行二十种操作,虽然数量不大,但是CPU的线程实际上非常有用,它们可以实现很多操作,并且运行速度非常快,而且它们的运作相互独立。
高端的GPU有成千上万个核,GPU第一个缺点就是它的每一个核运行速度都非常缓慢,第二个缺点是每一个核可以执行的操作没有CPU多,不能跟CPU的核和GPU的核进行直接比较。 GPU的核没有办法独立操作,它们需要共同协作,即GPU多个核共同执行同一项任务,而不是每个核做各自的事情。GPU有大量核数,当我们需要同时执行多种操作的时候,GPU并行处理能力非常棒,但这些事情本质上是相同的。GPU和CPU还有一点需要指明的是内存的概念,CPU有高速缓存,但是相对比较小,我们拥有CPU的大部分缓存,都是依赖于系统内存(在台式机上RAM的容量),而GPU在芯片中内置了RAM,如果是系统RAM和GPU通信,会带来严重的性能瓶颈,所以GPU基本上自己自带相对较大的内存。GPU也有自己的缓存系统,所以在GPU内存和GPU核之间有多级缓存,实际上跟CPU的多级缓存是相似的。
CPU对于通信处理来说是足够的,它们可以做各种各样的事,GPU更擅长于处理高度并行处理的算法,其中一个典型的性能很好又完全适用于GPU的一个算法就是矩阵乘法,CPU可能会串行计算。在这种平行任务,特别是矩阵变得非常庞大的时候,GPU表现更好。
1.2性能测试
CPUvsGPU:
在这些网络上做实验,GPU性能是CPU的64~76倍。
优化过的cuDNN VS 原生CUDA版代码:
所以用GPU时,我们应该使用cuDNN,可以获得更好的性能。
1.3实践中的问题
在实际中有一个问题是,在训练网络的时候模型可能存储在GPU上,比如模型的权重存储在GPU内存上,但是庞大的数据集却存储在电脑硬盘上,如果不小心就可能让从硬盘读取数据的环节成为训练速度的瓶颈,因为GPU速度非常快,它计算正向,反向传播的速度非常快,但是从旋转的机械硬盘上串行地读取数据会拖慢训练速度。有一些解决办法,如果训练数据集非常小,可以把整个数据集读到内存中,也可以用固态硬盘代替机械硬盘,这样读入速度会有较大提升。另外一些常用的思路是在CPU上使用多线程来预读数据,把数据读出内存或者读出硬盘,存储到内存上,这样就能连续不断地将缓存数据比较高效地送给GPU,但是这些做法不容易实现,因为GPU太快了,如果不快速将数据发送给GPU,仅仅读取数据这一项就会拖慢整个训练过程。
学生提问1:在写代码时,怎样才能有效地避免前面提到的问题?
从软件上来说,可能能做到的最有效的事情是设定好CPU的预读内容,比如避免这种比较笨的序列化操作,先把数据从硬盘里读出来,等待小批量的数据,一批一批地读完,然后依次送到GPU上,做正向和反向传播,按顺序这样做。如果有多个CPU线程在后台从硬盘中搬运出数据,这样可以把这些过程交错着运行起来,在GPU运行的同时,CPU的后台线程从硬盘中读取数据,主线程等待这些工作完成,在它们之间做一些同步化,让整个流程并行起来。但是这些实现起来比较麻烦,好在现在主流深度学习框架已经替我们完成了这些工作。
2.深度学习框架
被主流使用的第一代深度学习框架大多是由学术界完成的,但是下一代深度学习框架全部由工业届产生。
2.1 计算图思想和深度学习框架
无论何时我们进行深度学习,都要考虑构建一个计算图来计算任何我们想要计算的函数。比如在线性分类器的情况下,我们队数据X和权值W进行矩阵乘法或者做一些Loss函数来计算损失,也会使用一些正则项,我们企图把这些不同的操作拼起来,成为图结构。
在大型神经网络面前,这些图结构会非常复杂,有很多不同的层不同的激活函数,不同的权重,如果我们画成计算图会非常庞大,甚至不能画出来,所以深度学习框架意义重大,我们使用这些框架有三个主要原因:
(1)这些框架可以使我们非常轻松地构建和使用一个庞大地计算图,而且不用自己去管那些细节的东西。
(2)可以很方便地自动计算梯度,可以处理所有反向传播的细节,所有我们可以仅仅考虑如何写出我们网络的前向传播,反向传播会直接被给出。
(3)这些东西可以高效地在GPU上运行,我们不用关注一些底层的硬件上的细节,像cuBLAS,cuDNN,CUDA以及数据在CPU和GPU内存之间的移动。
我们可能能自己用numpy构建一些简单地神经网络,但是Numpy的缺点是它不能运行在GPU上,而且这种情况下只能自己计算梯度。
2.2 tensorflow
如图所示,tensorflow中通常可以把计算划分为两个主要阶段,1.首先我们先用一段代码来定义我们的计算图,就是上半部分红色框的部分,2.然后可以定义自己的图,这个图会运行数次,实际上可以将数据输入到计算图中,去实现任意想要的运算。这是tf中非常通用的一种模式,首先用一段代码构建图,然后运行图模型,重复利用它。
2.2.1代码分析
对红色框中代码进行分析:
从顶部的代码可以看到,我们定义了X,Y和W,并且创建了这些变量的tf.placeholder对象,所以这些变量会成为图中的输入结点,这些结点会是图中的入口结点,当我们运行图模型时,会输入数据将它们放到我们计算图中的输入槽中,然后我们用这些输入槽,就是这些符号变量,在这些符号变量上执行各种tensorflow操作以便构建我们想要运行在这些变量上的计算。因此基于这些情况我们做了一个矩阵乘法,使用tf.maximum来实现relu的非线性特性,然后用另一个矩阵乘法来计算我们的输出预测结果,然后我们又用基本的张量运算来计算欧式距离,以及计算目标值y和预测值之间的L2损失。需要指出的是,这里几行代码没做任何实质上的运算,目前系统里还没有任何数据,我们只是建立计算图数据结构,来告诉tensorflow当输入真实数据时,我们希望最终执行什么操作。因此这仅仅是建立图模型,没有做任何操作。然后在完成损失值运算后的一行代码是让tf去直接计算损失值在w1和w2方向上的梯度。
对蓝色框中代码进行分析:
这时候我们已经完成了计算图的构建,内存中已经存储了计算图数据结构的数据,现在我们进入一个tensorflow的会话,来实际运行计算图并且输入数据。一旦我们进入会话,就需要建立具体的数据,来输入给计算图。所以大多数时候,tf只是从Numpy数组接受数据,这里我们只是用Numpy为X Y和w1 w2创建具体的数值,并在字典中进行存储。这时候我们才是真正在运行计算图,可以看到我们调用了session.run来执行部分计算图的运算,并且计算需要的值然后向numpy数组再次返回具体的数值。至此我们只是在计算图上完成了正向和反向传播,如果想训练网络,我们只需添加几行代码