深度学习caffe的代码怎么读?

作者:Gein Chen 链接:https://www.zhihu.com/question/27982282/answer/80242005 来源:知乎 著作权归作者所有,转载请联系作者获得授权。

楼上的大神回答的都很好了,非常感谢。这里我想说一下我自己学习caffe的方式,限于时间篇幅,并不想深入到具体的实现细节,只是从大的方向上谈谈,因为讲清楚一个细节都是一篇博客的篇幅了。 1.学习程序的第一步,先让程序跑起来,看看结果,这样就会有直观的感受。 Caffe的官网上Caffe | Deep Learning Framework 提供了很多的examples,你可以很容易地开始训练一些已有的经典模型,如LeNet。我建议先从 LeNet MNIST Tutorial开始,因为数据集很小,网络也很小但很经典,用很少的时间就可以跑起来了。当你看到terminal刷拉拉的一行行输出,看到不断减少的loss和不断上升的accuracy,训练结束你得到了99+%的准确率,感觉好厉害的样子。你可以多跑跑几个例子,熟悉一下环境和接口。 2.单步调试,跟着Caffe在网络里流动 当玩了几天之后,你对Caffe的接口有点熟悉了,对已有的例子也玩腻了,你开始想看看具体是怎么实现的了。我觉得最好的方法是通过单步调试的方式跟着程序一步一步的在网络里前向传播,然后再被当成误差信息传回来。 Caffe就像一个你平常编程中Project,你可以使用IDE或者GDB去调试它,这里我们不细说调试的过程。你可以先跟踪前向传播的过程,无非就是从高层次到低层次的调用Forward函数, Solver->Net->Layer->Specific Layer (Convolution等...).后向传播也类似,但因为你对Caffe里面的各种变量运算不熟悉,当你跟踪完前向传播时可能已经头晕眼花了,还是休息一下,消化一下整个前向传播的流程。 刚刚开始你没有必要对每个Layer的计算细节都那么较真,大概知道程序的运算流程就好,这样你才可以比较快的对Caffe有个大体的把握。 3.个性化定制Caffe 到这里,你已经可以说自己有用过Caffe了,但是还不能算入门,因为你还不知道怎么修改源码,满足自己特定的需求。我们很多时候都需要自己定义新的层来完成特定的运算,这时你需要在Caffe里添加新的层。 你一开肯定无从下手,脑子一片空白。幸运的是Caffe github上的Wiki Development · BVLC/caffe Wiki · GitHub已经有了教程了,而且这是最接近latest Caffe的源码结构的教程,你在网上搜到的Blog很多是有点过时的,因为Caffe最近又重构了代码。你可以跟着它的指导去添加自己的层。 虽然你已经知道要在哪里添加自己的东西了,但你遇到最核心的问题是如何写下面这四个函数。

  • forward_cpu()
  • forward_gpu()
  • backward_cpu()
  • backward_gpu()

你可以先模仿已有的层去实现这四个函数,而且我相信forward函数很快就可以写出来了,但backward的还是一头雾水。这时我们就要补补神经网络里最核心的内容了——Backpropagation. 4.理解并实现Backpropagation 这个我觉得是与平台无关的,不管你是使用Caffe、Torch 7,还是Theano,你都需要深刻理解并掌握的。因为我比较笨,花了好长时间才能够适应推导中的各种符号。其实也不难,就是误差顺着Chain rule法则流回到前面的层。我不打算自己推导后向传播的过程,因为我知道我没有办法将它表达得很好,而且网上已经有很多非常好的教程了。下面是我觉得比较好的学习步骤吧。

  • 从浅层的神经网络(所谓的全连接层)的后向传播开始,因为这个比较简单,而且现在我们常说的CNN和LSTM的梯度计算也最终会回归到这里。
    • 第一个必看的是Ng深入浅出的Ufldl教程UFLDL Tutorial,还有中文版的,这对不喜欢看英语的同学是个好消息。当然你看一遍不理解,再看一遍,忘了,再看,读个几遍你才会对推导过程和数学符号熟悉。我头脑不大行,来来回回看了好多次。
    • 当然,Ufldl的教程有点短,我还发现了一个讲得更细腻清晰的教程, Michael Nielsen写的Neural networks and deep learning。它讲得实在太好了,以至于把我的任督二脉打通了。在Ufldl的基础上读这个,你应该可以很快掌握全连接层的反向传播。
    • 最后在拿出standford大牛karpathy的一篇博客Hacker's guide to Neural Networks,这里用了具体的编程例子手把手教你算梯度,并不是推导后向传播公式的,是关于通用梯度计算的。用心去体会一下。
  • 这时你跃跃欲试,回去查看Caffe源码里Convolution层的实现,但发现自己好像没看懂。虽说卷积层和全连接层的推导大同小异,但思维上还是有个gap的。我建议你先去看看Caffe如何实现卷积的,Caffe作者贾扬清大牛在知乎上的回答在 Caffe 中如何计算卷积?让我茅塞顿开。重点理解im2col和col2im.
  • 这时你知道了Convolution的前向传播,还差一点就可以弄明白后向传播怎么实现了。我建议你死磕Caffe中Convolution层的计算过程,把每一步都搞清楚,经过痛苦的过程之后你会对反向传播有了新的体会的。在这之后,你应该有能力添加自己的层了。再补充一个完整的添加新的层的教程Making a Caffe Layer • Computer Vision Enthusiast。这篇教程从头开始实现了一个Angle To Sine Cosine Layer,包含了梯度推导,前向与后向传播的CPU和GPU函数,非常棒的一个教程。
  • 最后,建议学习一下基本的GPU Cuda编程,虽然Caffe中已经把Cuda函数封装起来了,用起来很方便,但有时还是需要使用kernel函数等Cuda接口的函数。这里有一个入门的视频教程,讲得挺不错的NVIDIA CUDA初级教程视频

第二个回答,讲得还是一样的乱,谢谢观看。

作者:北川谦一 链接:https://www.zhihu.com/question/27982282/answer/39350629 来源:知乎 著作权归作者所有,转载请联系作者获得授权。

我是从Torch7转移到Caffe的人,仅供参考,当你阅读前你应该具备一些有关DL的基础知识,本文集中写Caffe代码结构而非介绍DL知识。 我是去年底开始看Caffe代码的,看代码的时间加在一起也不到一个月,也算半个新手,我的回答是从新手角度作一个入门阶段的经验分享。 本文只涉及Caffe结构的相关问题,不涉及具体实现技巧等细节。 ============================================================== 1. 初识Caffe 1.1. Caffe相对与其他DL框架的优点和缺点: 优点:

  • 速度快。Google Protocol Buffer数据标准为Caffe提升了效率。
  • 学术论文采用此模型较多。不确定是不是最多,但接触到的不少论文都与Caffe有关(R-CNN,DSN,最近还有人用Caffe实现LSTM)

缺点:

  • 曾更新过重要函数接口。有人反映,偶尔会出现接口变换的情况,自己很久前写的代码可能过了一段时间就不能和新版本很好地兼容了。(现在更新速度放缓,接口逐步趋于稳定,感谢 评论区王峰的建议)
  • 对于某些研究方向来说的人并不适合。这个需要对Caffe的结构有一定了解,(后面提到)。

1.2. Caffe代码层次。 回答里面有人说熟悉Blob,Layer,Net,Solver这样的几大类,我比较赞同。我基本是从这个顺序开始学习的,这四个类复杂性从低到高,贯穿了整个Caffe。把他们分为三个层次介绍。

  • Blob:是基础的数据结构,是用来保存学习到的参数以及网络传输过程中产生数据的类。
  • Layer:是网络的基本单元,由此派生出了各种层类。修改这部分的人主要是研究特征表达方向的。
  • Net:是网络的搭建,将Layer所派生出层类组合成网络。Solver:是Net的求解,修改这部分人主要会是研究DL求解方向的。

============================================================== 2. Caffe进阶 2.1. Blob: Caffe支持CUDA,在数据级别上也做了一些优化,这部分最重要的是知道它主要是对protocol buffer所定义的数据结构的继承,Caffe也因此可以在尽可能小的内存占用下获得很高的效率。(追求性能的同时Caffe也牺牲了一些代码可读性) 在更高一级的Layer中Blob用下面的形式表示学习到的参数:

vector<shared_ptr<Blob<Dtype> > > blobs_;

这里使用的是一个Blob的容器是因为某些Layer包含多组学习参数,比如多个卷积核的卷积层。 以及Layer所传递的数据形式,后面还会涉及到这里:

vector<Blob<Dtype>*> &bottom;
vector<Blob<Dtype>*> *top

2.2. Layer: 2.2.1. 5大Layer派生类型 Caffe十分强调网络的层次性,也就是说卷积操作,非线性变换(ReLU等),Pooling,权值连接等全部都由某一种Layer来表示。具体来说分为5大类Layer

  • NeuronLayer类 定义于neuron_layers.hpp中,其派生类主要是元素级别的运算(比如Dropout运算,激活函数ReLu,Sigmoid等),运算均为同址计算(in-place computation,返回值覆盖原值而占用新的内存)。
  • LossLayer类 定义于loss_layers.hpp中,其派生类会产生loss,只有这些层能够产生loss
  • 数据层 定义于data_layer.hpp中,作为网络的最底层,主要实现数据格式的转换。
  • 特征表达层(我自己分的类)定义于vision_layers.hpp(为什么叫vision这个名字,我目前还不清楚),实现特征表达功能,更具体地说包含卷积操作,Pooling操作,他们基本都会产生新的内存占用(Pooling相对较小)。
  • 网络连接层和激活函数(我自己分的类)定义于common_layers.hpp,Caffe提供了单个层与多个层的连接,并在这个头文件中声明。这里还包括了常用的全连接层InnerProductLayer类。

2.2.2. Layer的重要成员函数 在Layer内部,数据主要有两种传递方式,正向传导(Forward)反向传导(Backward)。Forward和Backward有CPU和GPU(部分有)两种实现。Caffe中所有的Layer都要用这两种方法传递数据。

virtual void Forward(const vector<Blob<Dtype>*> &bottom,                     
                     vector<Blob<Dtype>*> *top) = 0;
virtual void Backward(const vector<Blob<Dtype>*> &top,
                      const vector<bool> &propagate_down, 
                      vector<Blob<Dtype>*> *bottom) = 0;

Layer类派生出来的层类通过这实现这两个虚函数,产生了各式各样功能的层类。Forward是从根据bottom计算top的过程,Backward则相反(根据top计算bottom)。注意这里为什么用了一个包含Blob的容器(vector),对于大多数Layer来说输入和输出都各连接只有一个Layer,然而对于某些Layer存在一对多的情况,比如LossLayer和某些连接层。在网路结构定义文件(*.proto)中每一层的参数bottom和top数目就决定了vector中元素数目。

layers {
  bottom: "decode1neuron"   // 该层底下连接的第一个Layer
  bottom: "flatdata"        // 该层底下连接的第二个Layer
  top: "l2_error"           // 该层顶上连接的一个Layer
  name: "loss"              // 该层的名字
  type: EUCLIDEAN_LOSS      // 该层的类型
  loss_weight: 0
}

2.2.3. Layer的重要成员变量 loss

vector<Dtype> loss_;

每一层又有一个loss_值,只不多大多数Layer都是0,只有LossLayer才可能产生非0的loss_。计算loss是会把所有层的loss_相加。 learnable parameters

vector<shared_ptr<Blob<Dtype> > > blobs_;

前面提到过的,Layer学习到的参数。 2.3. Net: Net用容器的形式将多个Layer有序地放在一起,其自身实现的功能主要是对逐层Layer进行初始化,以及提供Update( )的接口(更新网络参数),本身不能对参数进行有效地学习过程。

vector<shared_ptr<Layer<Dtype> > > layers_;

同样Net也有它自己的

vector<Blob<Dtype>*>& Forward(const vector<Blob<Dtype>* > & bottom,
                              Dtype* loss = NULL);
void Net<Dtype>::Backward();

他们是对整个网络的前向和方向传导,各调用一次就可以计算出网络的loss了。 2.4. Solver 这个类中包含一个Net的指针,主要是实现了训练模型参数所采用的优化算法,它所派生的类就可以对整个网络进行训练了。

shared_ptr<Net<Dtype> > net_;

不同的模型训练方法通过重载函数ComputeUpdateValue( )实现计算update参数的核心功能

virtual void ComputeUpdateValue() = 0;

最后当进行整个网络训练过程(也就是你运行Caffe训练某个模型)的时候,实际上是在运行caffe.cpp中的train( )函数,而这个函数实际上是实例化一个Solver对象,初始化后调用了Solver中的Solve( )方法。而这个Solve( )函数主要就是在迭代运行下面这两个函数,就是刚才介绍的哪几个函数。

ComputeUpdateValue();
net_->Update();

============================================================== 至此,从底层到顶层对Caffe的主要结构都应该有了大致的概念。为了集中重点介绍Caffe的代码结构,文中略去了大量Caffe相关的实现细节和技巧,比如Layer和Net的参数如何初始化,proto文件的定义,基于cblas的卷积等操作的实现(cblas实现卷积这一点我的个人主页GanYuFei中的《Caffe学习笔记5-BLAS与boost::thread加速》有介绍)等等就不一一列举了。 整体来看Layer部分代码最多,也反映出Caffe比较重视丰富网络单元的类型,然而由于Caffe的代码结构高度层次化,使得某些研究以及应用(比如研究类似非逐层连接的神经网络这种复杂的网络连接方式)难以在该平台实现。这也就是一开始说的一个不足。 另外,Caffe基本数据单元都用Blob,使得数据在内存中的存储变得十分高效,紧凑,从而提升了整体训练能力,而同时带来的问题是我们看见的一些可读性上的不便,比如forward的参数也是直接用Blob而不是设计一个新类以增强可读性。所以说性能的提升是以可读性为代价的。 最后一点也是最重要的一点,我从Caffe学到了很多。第一次看的C++项目就看到这么好的代码,实在是受益匪浅,在这里也感谢作者贾扬清等人的贡献。

作者:米八 链接:https://www.zhihu.com/question/27982282/answer/55260722 来源:知乎 著作权归作者所有,转载请联系作者获得授权。

其实关于怎么用caffe,我一直想写篇文章,因为给太多人讲过怎么用了,也帮很多人基于caffe写过代码。14年初因为赶NIPS,开始用caffe,大概用了有一年半了。 先说个大概,知道了神经网络的结构,forward跟backward是怎样的一个过程,基本上就知道了caffe的结构了。按照神经网络的运行过程看Caffe就好了。 既然说神经网络,那么就得有一个网络出来,caffe里面就用Net这个类来记录这个网络。 那么网络是由很多个layer构成,自然而然就会有Layer这个类,为了统一管理这些类,自然而然就想要抽象,那么Layer这个抽象类作为一个最基本的单元出现了,接下来就会有实现各种功能的layer出现,如:Convolution/ReLU/Softmax等。 Layer间需要连接啊,Layer需要有输入输出啊,caffe里面用Bottom来表示输入,Top表示输出,前一层Layer的top是后一层layer的bottom,这样,连接搞掂了,输入输出也搞掂了。 网络搞掂了,layer搞掂了,那就要搞个求解算法啊,那么Solver这个类就出现了,这个就是用来求解网络的。 就酱紫,有时间再写得详细一点。反正就这么一句话:按照神经网络的运行过程看Caffe就好了。

深度学习源码解读-ch0-talk is cheap - 黑客与画家 - 知乎专栏 深度学习源码解读-ch1-JSON is awesome - 黑客与画家 - 知乎专栏 深度学习源码解读-ch2-Caffe is coming - 黑客与画家 - 知乎专栏 深度学习源码解读-ch3-Caffe is brewing - 黑客与画家 - 知乎专栏 深度学习源码解读-ch4-Caffe 中的设计模式 - 黑客与画家 - 知乎专栏 作者:Vinjn张静 链接:https://www.zhihu.com/question/27982282/answer/77895063 来源:知乎 著作权归作者所有,转载请联系作者获得授权。

作者:Fiberleif 链接:https://www.zhihu.com/question/27982282/answer/94031317 来源:知乎 著作权归作者所有,转载请联系作者获得授权。

正好最近的工作与Caffe相关,就把网上已有的一些资料以及自己 读代码时候的亲身经历简单地整理了一下,希望能对大家有所帮助~ ================================================================ Caffe's Abstract Framework: 就像楼上一些回答里说到的,要读懂caffe,首先要熟悉Blob,Layer,Net,Solver这几个大类。这四个大类自下而上,环环相扣,贯穿了整个caffe的结构,下面先分别简单地介绍一下这四个类的主要作用。

  • Blob:作为数据传输的媒介,无论是网络权重参数,还是输入数据,都是转化为Blob数据结构来存储
  • Layer:作为网络的基础单元,神经网络中层与层间的数据节点、前后传递都在该数据结构中被实现,层类种类丰富,比如常用的卷积层、全连接层、pooling层等等,大大地增加了网络的多样性
  • Net:作为网络的整体骨架,决定了网络中的层次数目以及各个层的类别等信息
  • Solver:作为网络的求解策略,涉及到求解优化问题的策略选择以及参数确定方面,修改这个模块的话一般都会是研究DL的优化求解的方向。

================================================================ Caffe's Concrete Framework: 1. Blob: 1.1. Blob的类型描述 Caffe内部采用的数据类型主要是对protocol buffer所定义的数据结构的继承,因此可以在尽可能小的内存占用下获得很高的效率,虽然追求性能的同时Caffe也会牺牲了一些代码可读性。 直观来说,可以把Blob看成一个有4维的结构体(包含数据和梯度),而实际上,它们只是一维的指针而已,其4维结构通过shape属性得以计算出来。 1.2. Blob的重要成员函数和变量

shared_ptr<SyncedMemory> data_ //数据
shared_ptr<SyncedMemory> diff_ //梯度
void Blob<Dtype>::Reshape(const int num, const int channels, const int height,
const int width)

重新修改Blob的形状(4维),并根据形状来申请动态内存存储数据和梯度。

inline int count(int start_axis, int end_axis) const

计算Blob所需要的基本数据单元的数量。2. Layer: 2.1. Layer的类型描述 Layer是网络模型和计算的核心,在数据存储上,主要分成bottom_vecs、top_vecs、weights&bias三个部分;在数据传递上,也主要分为LayerSetUp、Reshape、Forward、Backward四个过程,符合直观上对层与层之间连接的理解,贴切自然。 2.2. Layer的重要成员函数和变量

vector<Dtype> loss_ //每一层都会有一个loss值,但只有LossLayer才会产生非0的loss
vector<shared_ptr<Blob<Dtype> > > blobs_ //Layer所学习的参数,包括权值和偏差
virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) 

通过bottom Blob对象的形状以及LayerParameter(从prototxt读入)来确定Layer的学习参数(以Blob类型存储)的形状。

 virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) 

通过bottom Blob对象的形状以及Layer的学习参数的形状来确定top Blob对象的形状。

virtual void Forward(const vector<Blob<Dtype>*> &bottom,                     
                     vector<Blob<Dtype>*> *top) = 0

Layer内部数据正向传播,从bottom到top方向。

virtual void Backward(const vector<Blob<Dtype>*> &top,
                      const vector<bool> &propagate_down, 
                      vector<Blob<Dtype>*> *bottom) = 0

Layer内部梯度反向传播,从top到bottom方向。 3. Net: 3.1. Net的类型描述 Net用容器的形式将多个Layer有序地放在一起,其自身实现的功能主要是对逐层Layer进行初始化,以及提供Update( )的接口(更新网络参数),本身不能对参数进行有效地学习过程。 3.2. Net的重要成员函数和变量

vector<shared_ptr<Layer<Dtype> > > layers_ //构成该net的layers
vector<vector<Blob<Dtype>*> > bottom_vecs_ //每一层layer中的bottom Blobs
vector<vector<Blob<Dtype>*> > top_vecs_ //每一层layer中的top Blobs
vector<shared_ptr<Blob<Dtype> > > params_ //整个net中的learnable parameter
void Init(const NetParameter& param)

根据NetParameter进行net初始化,简单的来说就是先把网络中所有层的bottom Blobs&top Blobs(无重复)实例化,并从输入层开始,逐层地进行Setup的工作,从而完成了整个网络的搭建,为后面的数据前后传输打下基础。

vector<Blob<Dtype>*>& Forward(const vector<Blob<Dtype>* > & bottom,
                              Dtype* loss = NULL)
void Net<Dtype>::Backward()

是对整个网络的前向和方向传导,各调用一次就可以计算出网络的loss了。 4. Solver 4.1. Solver的类型描述 Solver类中包含一个Net的指针,主要是实现了训练模型参数所采用的优化算法,根据优化算法的不同会派生不同的类,而基于这些子类就可以对网络进行正常的训练过程。 4.2. Solver的重要成员函数和变量

shared_ptr<Net<Dtype> > net_ //net对象
void Step(int iters)

对已初始化后的网络进行固定次数的训练迭代过程。

ComputeUpdateValue();
net_->Update();

不同的模型训练方法通过重载函数ComputeUpdateValue( )实现计算update参数的核心功能。 =============================================================== 说到这儿,大家可能已经对Caffe的自底向上的主要结构有了一个大致的了解~ 除了这些之外呢,Caffe里面还涉及到了许多特色模块,比如google的轻量级高效数据传输工具protobuf、负责stream输出模块的glog和负责命令行便捷输入的gflags、factory宏定义等等... 最后想在这里向caffe的contributors以及caffe blog的博主们表示深深的感激~ 由于本人的C++经验不多,所以是花了较长的时间才大致理解caffe的实现(呜呜TT),但现在回头想来确实收获很大,不管是在代码架构方面,还是在实现细节方面,caffe都有很多值得借鉴的地方,相信大家在阅读完caffe之后也一定会受益匪浅~~~

作为一个caffe的初学者,感觉上边几位高票的答案已经写的非常的好了。 但是我发现有一个普林斯顿大学Vision Group的caffe tutorial PPT做的很不错,因此想和大家分享下。 链接: http://robots.princeton.edu/courses/COS598/2015sp/slides/Caffe/caffe_tutorial.pdf p.s. 感觉nsight IDE不错,nvidia出品。安装也很简单:

sudo apt-get install nvidia-nsight

关于nsight IDE如何进行caffe的调试,可以参见这里:Build and Debug Caffe in Eclipse Nsight

作者:哈哈冻 链接:https://www.zhihu.com/question/27982282/answer/99796828 来源:知乎 著作权归作者所有,转载请联系作者获得授权。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏机器之心

资源 | 谷歌官方开源tf-seq2seq:一种通用编码器-解码器框架

选自Google 机器之心编译 参与:吴攀 谷歌又开源了!tf-seq2seq 是一个用于 TensorFlow 的通用编码器-解码器框架(encoder-de...

32770
来自专栏python开发者

字符型图片验证码识别完整过程及Python实现

验证码是目前互联网上非常常见也是非常重要的一个事物,充当着很多系统的 防火墙 功能,但是随时OCR技术的发展,验证码暴露出来的安全问题也越来越严峻。本文介绍了一...

2.5K80
来自专栏互联网杂技

A*算法详解

概述 虽然掌握了 A* 算法的人认为它容易,但是对于初学者来说, A* 算法还是很复杂的。 搜索区域(The Search Area) 我们假设某人要从 A 点...

46690
来自专栏机器之心

教程 | 如何保持运动小车上的旗杆屹立不倒?TensorFlow利用A3C算法训练智能体玩CartPole游戏

本教程面向所有对强化学习感兴趣的人,不会涉及太深的机器学习基础,但主题中涵盖了高级策略网络和价值网络的相关知识。此外,我建议阅读 Voldymyr Mnih 的...

19730
来自专栏应兆康的专栏

扭曲你的数据,让其变得具有视觉吸引力

经常有这样的情况,你用数据画出图像有看起来会很丑,如何让你的图像变得好看一点呢?本文给大家介绍如何扭曲你的数据,在不影响结果和其他属性的情况下,使得你数据画出来...

40840
来自专栏大数据文摘

核心算法|谷歌如何从网络的大海里捞到针

21980
来自专栏ml

faster-rcnn中ROI_POOIING层的解读

在没有出现sppnet之前,RCNN使用corp和warp来对图片进行大小调整,这种操作会造成图片信息失真和信息丢失。sppnet这个模型推出来之后(关于这个网...

52490
来自专栏MelonTeam专栏

一种用于短文本的神经响应机

导语 这篇文章是翻译别人的,来源是https://arxiv.org/abs/1503.02364 摘要 我们提出了神经响应机(NRM),一种基于...

20080
来自专栏量子位

AI跟Bob Ross学画画,杂乱色块秒变风景油画 | PyTorch教程+代码

王新民 编译整理 量子位 出品 | 公众号 QbitAI 正在研究机器学习的全栈码农Dendrick Tan在博客上发布了一份教程+代码:用PyTorch实现将...

38750
来自专栏华章科技

核心算法:谷歌如何从网络的大海里捞到针

作 者: David Austin,Grand Valley State University

11480

扫码关注云+社区

领取腾讯云代金券