深度学习的大部分知识都很深奥,作为一个深度学习领域的小白,想要了解一下深度学习分为哪些大致的框架,分别的内容又有哪些?哪些框架可以通过腾讯云服务器来开始学习呢?
深度学习是执行更高级别复杂任务的关键,成功构建和部署它们对全球数据科学家和数据工程师来说是一个巨大的挑战。今天,我们拥有无数的框架,使我们能够开发出可以提供更好抽象级别的工具,同时简化困难的编程挑战。
每个框架都以不同的方式构建,以用于不同的目的。介绍以下这8个深度学习框架,以便您更好地了解哪个框架最适合您,或者在解决您的业务挑战时更方便。
可以说是最好的深度学习框架之一,现在已被大规模的几家巨头采用,如空中客车,Twitter,IBM等,这主要归功于其高度灵活的系统架构。
最着名的TensorFlow用例必须是谷歌翻译,加上自然语言处理,文本分类/摘要,语音/图像/手写识别,预测和标记等功能。
TensorFlow可在桌面和移动设备上使用,并且还支持Python,C ++和R等语言,以创建深度学习模型以及包装器库。
TensorFlow附带2个广泛使用的工具 -
如果您在深度学习方面碰巧迈出了第一步,那么您应该选择TensorFlow,这是基于Python的,由Google支持,并附带文档和演练来指导您。
Caffe是一个深度学习框架,支持C,C ++,Python,MATLAB等接口以及命令行界面。众所周知,它的速度和可转换性及其在卷积神经网络(CNN)建模中的适用性。使用Caffe的C ++库(附带Python接口)的最大好处是从深度网络存储库“Caffe Model Zoo”访问可用网络,这些网络已经过预先培训,可以立即使用。无论是建模CNN还是解决图像处理问题,这都必须成为首选图书馆。
Caffe最大的USP就是速度。它可以使用单个Nvidia K40 GPU每天处理超过六千万张图像。这是用于推理的1 ms /图像和用于学习的4 ms /图像,更新的库版本仍然更快。
Caffe是一种流行的视觉识别深度学习网络。但是,Caffe不支持像TensorFlow或CNTK中那样的细粒度网络层。鉴于该体系结构,对循环网络和语言建模的总体支持非常差,并且建立复杂的层类型必须以低级语言完成。
Microsoft Cognitive Toolkit(以前称为CNTK)是一种开源深度学习框架,用于训练深度学习模型,因其易于培训和跨服务器的流行模型类型而闻名。它执行有效的卷积神经网络和基于图像,语音和文本的数据培训。与Caffe类似,它受Python,C ++和命令行界面等接口的支持。
鉴于其资源的连贯使用,可以使用工具包轻松完成强化学习模型或生成对抗网络(GAN)。众所周知,与在Theano或TensorFlow等工具包上运行时,在多台机器上运行时,可提供更高的性能和可扩展性。
与Caffe相比,在发明新的复杂层类型时,由于构建块的精细粒度,用户不需要以低级语言实现它们。Microsoft Cognitive Toolkit支持RNN和CNN类型的神经模型,因此能够处理图像,手写和语音识别问题。目前,由于缺乏对ARM架构的支持,移动设备的功能相当有限。
Torch是一种科学计算框架,可为机器学习算法提供广泛支持。它是一个基于Lua的深度学习框架,广泛应用于Facebook,Twitter和Google等行业巨头。它采用CUDA和C / C ++库进行处理,基本上是为了扩展建筑模型的生产和整体灵活性。
毕竟,PyTorch已经在深度学习框架社区中获得了很高的采用率,并且被认为是TensorFlow的竞争对手。PyTorch基本上是Torch深度学习框架的一个端口,用于构建深度神经网络和执行高度复杂的张量计算。
与Torch相反,PyTorch在Python上运行,这意味着任何对Python有基本了解的人都可以开始构建他们自己的深度学习模型。
鉴于PyTorch框架的架构风格,与Torch相比,整个深度建模过程更简单,更透明。
MXNet(发音为mix-net)专为高效率,高生产率和灵活性而设计,是一个深度学习框架,由Python,R,C ++和Julia支持。
MXNet的优点在于它为用户提供了使用各种编程语言(Python,C ++,R,Julia和Scala等)编写代码的能力。这意味着您可以使用您喜欢的任何语言训练您的深度学习模型,而无需从头学习新东西。使用C ++和CUDA编写的后端,MXNet能够扩展和使用无数的GPU,这使得它对企业来说是不可或缺的。例如 - 亚马逊使用MXNet作为深度学习的参考库。
MXNet支持长短期内存(LTSM)网络以及RNN和CNN。
这种深度学习框架以其在成像,手写/语音识别,预测以及NLP方面的能力而闻名。
高度强大,动态和直观 - Chainer是一个基于Python的神经网络深度学习框架,它是根据运行策略定义而设计的。与使用相同策略的其他框架相比,您可以在运行时修改网络,从而允许您执行任意控制流语句。
Chainer支持CUDA计算和多GPU。该深度学习框架主要用于使用RNN和CNN进行情感分析,机器翻译,语音识别等。
Keras神经网络库(支持接口 - Python)以极简主义而闻名,支持卷积网络和循环网络,能够在TensorFlow或Theano上运行。该库是用Python编写的,并且作为其USP开发了快速实验。
由于TensorFlow接口有点具有挑战性,而且它是一个低级库,可能对新用户来说很复杂,因此Keras的构建旨在通过构建有效的快速原型设计提供简单的界面。可以使用TensorFlow的神经网络。
轻量级,易于使用,并且通过堆叠多个层来构建深度学习模型非常简单 - 简而言之就是Keras。这就是为什么Keras成为TensorFlow核心API的一部分的原因。
Keras的主要用途是分类,文本生成和摘要,标记,翻译以及语音识别等。如果您恰好是具有Python经验的开发人员并希望深入学习,那么Keras肯定是值得一试的。
通过迭代减少,微服务架构适配以及分布式CPU和GPU的并行训练是Deeplearning4j深度学习框架的一些显着特征。它是用Java和Scala开发的,也支持其他JVM语言。
广泛采用作为商业,行业为重点的分布式深度学习平台,这个深度学习框架的最大优势是,您可以将整个Java生态系统整合在一起,以执行深度学习,并且可以在Hadoop和Spark之间进行管理以协调编排多个主机线程。DL4J使用map reduce来训练网络,同时依赖于其他库来执行大型矩阵操作。
Deeplearning4j通过RBM,DBN,卷积神经网络(CNN),递归神经网络(RNN),递归神经张量网络(RNTN)和长期短期记忆(LTSM)提供深度网络支持。
由于这个深度学习框架是用Java实现的,因此与Python相比,它更有效。当涉及使用多个GPU的图像识别任务时,它与Caffe一样快。该框架显示出无与伦比的图像识别,欺诈检测,文本挖掘,词性标注和自然语言处理的潜力。
使用Java作为您的核心编程语言,如果您正在寻找一种将深度学习模型部署到生产的强大而有效的方法,那么您当然应该选择这种深度学习框架。
很明显,深度学习的出现已经引发了许多机器学习和人工智能的实际使用案例。深度学习可能以最简单的方式分解任务,以便以最有效的方式协助机器。
话虽如此,上面列表中哪个深度学习框架最适合您的要求?对此的答案取决于许多因素,但是,如果您只是想要开始,那么基于Python的深度学习框架(如TensorFlow或Chainer)应该是您的选择。如果您恰好经验丰富,在选择最佳深度学习框架之前,您需要考虑速度,资源需求和使用情况以及训练模型的一致性。
深度学习框架通过高级编程接口为深度神经网络的设计,培训和验证提供了构建模块。广泛使用的深度学习框架,如Caffe2,Cognitive工具包,MXNet,PyTorch,TensorFlow等,依靠GPU加速库(如cuDNN和NCCL)来提供高性能的多GPU加速培训。
开发人员,研究人员和数据科学家可以轻松访问NVIDIA优化的深度学习框架容器,这些容器的性能已针对NVIDIA GPU进行了调整和测试。这消除了管理包和依赖关系或从源构建深度学习框架的需要。访问NVIDIA GPU云(NGC)以了解更多信息并开始使用。
以下是流行的深度学习框架列表,包括学习资源和入门资源的链接。
Caffe2是一个深度学习框架,旨在通过友好的基于python的API轻松表达所有模型类型,例如CNN,RNN等,并使用高效的C ++和CUDA后端执行它们。用户可以灵活地使用python中的高级和表达式操作组合来组装模型,从而可以轻松实现可视化,或者序列化创建的模型并直接使用底层C ++实现。Caffe2支持单GPU和多GPU执行,并支持多节点执行。
型号部署:
对于Cognitive Toolkit训练模型的高性能推理部署,导出为ONNX格式,并使用NVIDIA TensorRT推理加速器进行优化和部署。
Microsoft Cognitive Toolkit,以前称为CNTK,是一个统一的深度学习工具包,它通过有向图将神经网络描述为一系列计算步骤。在该有向图中,叶节点表示输入值或网络参数,而其他节点表示其输入上的矩阵运算。
对于Cognitive Toolkit训练模型的高性能推理部署,导出为ONNX格式,并使用NVIDIA TensorRT推理加速器进行优化和部署。
MATLAB使工程师,科学家和领域专家能够轻松深入学习。借助用于管理和标记大型数据集的工具和功能,MATLAB还提供专用工具箱,用于处理机器学习,神经网络,计算机视觉和自动驾驶。只需几行代码,MATLAB就可以创建和可视化模型,并将模型部署到服务器和嵌入式设备,而无需专家。MATLAB还使用户能够从MATLAB代码自动生成用于深度学习和视觉应用的高性能CUDA代码。
对于MATLAB训练模型的高性能推理部署,使用MATLAB GPU Coder自动生成从云到嵌入式部署环境的TensorRT优化推理引擎。
MXNet是一个旨在提高效率和灵活性的深度学习框架。它允许您混合符号编程和命令式编程的风格,以最大限度地提高效率和生产力。
其核心是动态依赖项调度程序,可以动态地自动并行化符号和命令操作。最重要的图形优化层使符号执行更快,内存效率更高。该库既便携又轻巧,可扩展到多个GPU和多台机器。
对于MXNet训练模型的高性能推理部署,导出为ONNX格式,并使用NVIDIA TensorRT推理加速器进行优化和部署。
Caffe由伯克利视觉与学习中心(BVLC)和社区贡献者共同开发。NVIDIA Caffe,也称为NVCaffe,是NVIDIA维护的BVLC Caffe分支,专为NVIDIA GPU而设计,特别是在多GPU配置中。
对于Caffe训练模型的高性能推理部署,使用NVIDIA TensorRT内置的Caffe模型导入器进行导入,优化和部署。
PyTorch是一个Python包,提供两个高级功能:
您可以重用您喜欢的Python包,例如numpy,scipy和Cython,以在需要时扩展PyTorch。
对于训练模型的高性能推理部署,导出为ONNX格式,并使用NVIDIA TensorRT推理加速器进行优化和部署。
TensorFlow是一个使用数据流图进行数值计算的开源软件库。图中的节点表示数学运算,而图表边表示在它们之间流动的多维数据阵列(张量)。这种灵活的体系结构允许您将计算部署到桌面,服务器或移动设备中的一个或多个CPU或GPU,而无需重写代码。为了可视化TensorFlow结果,TensorFlow提供了TensorBoard,这是一套可视化工具。
对于TensorFlow训练模型的高性能推理部署,您可以:
Chainer是一个基于Python的深度学习框架,旨在实现灵活性。它提供基于逐个定义方法的自动差异化API,也称为动态计算图形,以及用于构建和训练神经网络的面向对象的高级API。它使用CuPy支持CUDA和cuDNN,以进行高性能的培训和推理。
对于Chainer训练模型的高性能推理部署,导出为ONNX格式,并使用NVIDIA TensorRT推理加速器进行优化和部署。
PaddlePaddle提供了一个直观而灵活的界面,用于加载数据和指定模型结构。它支持CNN,RNN,多种变体,并可轻松配置复杂的深层模型。
它还提供极其优化的操作,内存回收和网络通信。PaddlePaddle可以轻松扩展异构计算资源和存储,从而加速培训过程。
我个人非常推荐通过使用腾讯云的GPU服务器来学习深度学习框架。便宜简单极易入门。你可以去腾讯云GPU服务器页面查看。腾讯云GPU服务器主要则是使用了TensorFlow框架来搭建深度神经网络。大致操作如下:
在 r1.1版本的 Tensorflow 中,已经集成了以前的 Keras 模块,使得搭建基本的 Tensorflow
模块更加简单、方便。
我们可以简单的将深度神经网络的模块,分成以下的三个部分,即深度神经网络上游的基于生成器的 输入模块,深度神经网络本身,以及深度神经网络下游基于批量梯度下降算法的 凸优化模块:
其中,搭建深度神经网络的零件又可以分成以下类别:
需要强调一下,这些层与之前一样,都 同时包括了正向传播、反向传播两条通路。我们这里只介绍比较好理解的正向传播过程,基于其导数的反向过程同样也是存在的,其代码已经包括在 Tensorflow 的框架中对应的模块里,可以直接使用。
当然还有更多的零件,具体可以去keras 文档中参阅。
接下来的部分,我们将首先介绍这些深度神经网络的零件,然后再分别介绍上游的批量输入模块,以及下游的凸优化模块。
Dense 层,就是我们上一篇文章里提到的 Linear
层,即 y=wx+b ,计算乘法以及加法。
Activation 层在我们上一篇文章中,同样出现过,即 Tanh
层以及Sigmoid
层,他们都是 Activation 层的一种。当然 Activation 不止有这两种形式,比如有:
图片来源 激活函数与caffe及参数
这其中 relu
层可能是深度学习时代最重要的一种激发函数,在2011年首次被提出。由公式可见,relu
相比早期的 tanh
与 sigmoid
函数, relu
有两个重要的特点,其一是在较小处都是0(sigmoid
,relu
)或者-5(tanh
),但是较大值relu
函数没有取值上限。其次是relu
层在0除不可导,是一个非线性的函数:
即 y=x*(x>0)
对其求导,其结果是:
Dropout
层,指的是在训练过程中,每次更新参数时将会随机断开一定百分比(rate)的输入神经元,这种方式可以用于防止过拟合。
图片来源Dropout: A Simple Way to Prevent Neural Networks from Overfitting
Flatten
层,指的是将高维的张量(Tensor, 如二维的矩阵、三维的3D矩阵等)变成一个一维张量(向量)。Flatten
层通常位于连接深度神经网络的 卷积层部分 以及 全连接层部分。
提到卷积层,就必须讲一下卷积神经网络。我们在第一讲的最后部分提高深度学习模型预测的准确性部分,提了一句 “使用更复杂的深度学习网络,在图片中挖出数以百万计的特征”。这种“更复杂的神经网络”,指的就是卷积神经网络。卷积神经网络相比之前基于 Dense
层建立的神经网络,有所区别之处在于,卷积神经网络可以使用更少的参数,对局部特征有更好的理解。
我们这里以2D 的卷积神经网络为例,来逐一介绍卷积神经网络中的重要函数。比如我们使用一个形状如下的卷积核:
1 | 0 | 1 |
0 | 1 | 0 |
1 | 0 | 1 |
扫描这样一个二维矩阵,比如一张图片:
1 | 1 | 1 | 0 | 0 |
0 | 1 | 1 | 1 | 0 |
0 | 0 | 1 | 1 | 1 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 1 | 0 | 0 |
其过程与结果会是这样:
图片来源kdnuggets
当然,这里很重要的一点,就是正如我们上一讲提到的, Linear
函数的 w
, b
两个参数都是变量,会在不断的训练中,不断学习更新。卷积神经网络中,卷积核其实也是一个变量。这里的
1 | 0 | 1 |
0 | 1 | 0 |
1 | 0 | 1 |
可能只是初始值,也可能是某一次迭代时选用的值。随着模型的不断训练,将会不断的更新成其他值,结果也将会是一个不规则的形状。具体的更新方式,同上一讲提到的 Linear
等函数模块相同,卷积层也有反向传播函数,基于反向函数计算梯度,即可用来更新现有的卷积层的值,具体方法可参考CNN的反向传导练习。举一个经过多次学习得到的卷积神经网络的卷积核为例:
AlexNet | 第一层卷积层的96个、大小为 11x11 的卷积核结果矩阵。 |
---|---|
图片来源Alexnet 2012年文章
清楚了其原理,卷积神经网络还需要再理解几个输入参数:
Conv2D(filters, kernel_size, strides=(1, 1), padding='valid', ...)
其中:
filters
指的是输出的卷积层的层数。如上面的动图,只输出了一个卷积层,filters = 1
,而实际运用过程中,一次会输出很多卷积层。kernel_size
指的是卷积层的大小,是一个 二维数组,分别代表卷积层有几行、几列。strides
指的是卷积核在输入层扫描时,在 x,y 两个方向,每间隔多长扫执行一次扫描。padding
这里指的是是否扫描边缘。如果是 valid
,则仅仅扫描已知的矩阵,即忽略边缘。而如果是 same
,则将根据情况在边缘补上0,并且扫描边缘,使得输出的大小等于 input_size / strides。这里 Cropping2D
就比较好理解了,就是特地选取输入图像的某一个固定的小部分。比如车载摄像头检测路面的马路线时,摄像头上半部分拍到的天空就可以被 Cropping2D
函数直接切掉忽略不计。
图片来源Udacity自动驾驶课程
1.2.1部分提到输入参数时,提到 padding
参数如果是same
,扫描图像边缘时会补上0,确保输出数量等于 input / strides。这里 ZeroPadding2D 的作用,就是在图像外层边缘补上几层0。如下图,就是对原本 32x32x3 的图片进行 ZeroPadding2D(padding=(2, 2))
操作后的结果:
图片来源Adit Deshpande博客
可能大家在上一部分会意识到一点,就是通过与一个相同的、大小为11x11的卷积核做卷积操作,每次移动步长为1,则相邻的结果会非常接近,正是由于结果接近,有很多信息是冗余的。
因此,MaxPooling
就是一种减少模型冗余程度的方法。以 2x 2 MaxPooling
为例。图中如果是一个 4x4 的输入矩阵,则这个 4x4 的矩阵,会被分割成由两行、两列组成的 2x2 子矩阵,然后每个 2x2 子矩阵取一个最大值作为代表,由此得到一个两行、两列的结果:
图片来源 斯坦福CS231课程
AveragePooling
与 MaxPooling
类似,不同的是一个取最大值,一个是平均值。如果上图的 MaxPooling
换成 AveragePooling2D
,结果会是:
3.25 | 5.25 | |
2 | 2 |
GlobalAveragePooling
,其实指的是,之前举例 MaxPooling
提到的 2x2 Pooling,对子矩阵分别平均,变成了对整个input 矩阵求平均值。
这个理念其实和池化层关系并不十分紧密,因为他扔掉的信息有点过多了,通常只会出现在卷积神经网络的最后一层,通常是作为早期深度神经网络 Flatten
层 + Dense
层结构的替代品:
前面提到过 Flatten
层通常位于连接深度神经网络的 卷积层部分 以及 全连接层部分,但是这个连接有一个大问题,就是如果是一个 1k x 1k 的全连接层,一下就多出来了百万参数,而这些参数实际用处相比卷积层并不高。造成的结果就是,早期的深度神经网络占据内存的大小,反而要高于后期表现更好的神经网络:
更重要的是,全连接层由于参数偏多,更容易造成 过拟合——前文提到的 Dropout
层就是为了避免过拟合的一种策略,进而由于过拟合,妨碍整个网络的泛化能力。于是就有了用更多的卷积层提取特征,然后不去 Flatten
这些 k x k 大小卷积层,直接把这些 k x k 大小卷积层变成一个值,作为特征,连接分类标签。
除了之前提到的 Dropout
策略,以及用 GlobalAveragePooling
取代全连接层的策略,还有一种方法可以降低网络的过拟合,就是正则化,这里着重介绍下 BatchNormalization
。
BatchNormalization
确实适合降低过拟合,但他提出的本意,是为了加速神经网络训练的收敛速度。比如我们进行最优值搜索时,我们不清楚最优值位于哪里,可能是上千、上万,也可能是个负数。这种不确定性,会造成搜索时间的浪费。
BatchNormalization
就是一种将需要进行最优值搜索数据,转换成标准正态分布,这样optimizer
就可以加速优化:
输入:一批input 数据: B
期望输出: β,γ
具体如何实现正向传播和反向传播,可以看这里。
最后再谈一谈和图像分割相关的反卷积层。
之前在 1.2 介绍了卷积层,在 1.3 介绍了池化层。相信读者大概有了一种感觉,就是卷积、池化其实都是在对一片区域计算平均值、最大值,进而忽略这部分信息。换言之,卷积+池化,就是对输入图片打马赛克。
但是马赛克是否有用?我们知道老司机可以做到“图中有码,心中无码”,就是说,图片即便是打了马赛克、忽略了细节,我们仍然可以大概猜出图片的内容。这个过程,就有点反卷积的意思了。
利用反卷积层,可以基于 卷积层+全连接层结构,构建新的、用于图像分割的神经网络 结构。这种结构不限制输入图片的大小,
卷积+全连接层结构 | 全卷积层结构 |
---|---|
图片来源:Fully Convolutional Networks for Semantic Segmentation
上图在最后阶段使用了 Upsampling
模块,这个同样在 Tensorflow 的 keras 模块可以找到。用法和 MaxPooling2D
基本相反,比如:
UpSampling2D(size=(2, 2))
就相当于将输入图片的长宽各拉伸一倍,整个图片被放大了。
当然,Upsampling
实际上未必在网络的最后才使用,我们后面文章提到的 unet 网络结构,每一次进行卷积操作缩小图片大小,后期都会使用 Upsampling
函数增大图片。
图片来源 U-Net: Convolutional Networks for Biomedical Image Segmentation
介绍完深度神经网络的基本结构以后,读者可能已经意识到了,1.3.3 部分提到的深度神经网络的参数大小动辄几十M、上百M,如何合理训练这些参数是个大问题。这就需要在这个网络的上下游,合理处理这个问题。
海量参数背后的意义是,深度神经网络可以获取海量的特征。第一讲中提到过,深度学习是脱胎于传统机器学习的,两者之间的区别,就是深度学习可以在图像处理中,自动进行特征工程,如我们第一讲所言:
想让计算机帮忙挖掘、标注这些更多的特征,这就离不开 更优化的模型 了。事实上,这几年深度学习领域的新进展,就是以这个想法为基础产生的。我们可以使用更复杂的深度学习网络,在图片中挖出数以百万计的特征。
这时候,问题也就来了。机器学习过程中,是需要一个输入文件的。这个输入文件的行、列,分别指代样本名称以及特征名称。如果是进行百万张图片的分类,每个图片都有数以百万计的特征,我们将拿到一个 百万样本 x 百万特征 的巨型矩阵。传统的机器学习方法拿到这个矩阵时,受限于计算机内存大小的限制,通常是无从下手的。也就是说,传统机器学习方法,除了在多数情况下不会自动产生这么多的特征以外,模型的训练也会是一个大问题。
深度学习算法为了实现对这一量级数据的计算,做了以下算法以及工程方面的创新:
这就有点《愚公移山》的意思了。我们可以把训练深度神经网络的训练任务,想象成是搬走一座大山。成语故事中,愚公的办法是既然没有办法直接把山搬走,那就子子孙孙,每人每天搬几筐土走,山就会越来越矮,总有一天可以搬完——这种任务分解方式就如同深度学习算法的分批训练方式。同时,随着科技进步,可能搬着搬着就用翻斗车甚至是高达来代替背筐,就相当于是用 GPU 等高并行计算卡代替了 CPU。
于是,我们这里将主要提到的上游输入模块,以及下游凸优化模块,实际上就是在说如何使用愚公移山的策略,用 少量多次 的方法,去“搬”深度神经网络背后大规模计算量这座大山。
这一部分实际是在说,当我们有成千上万的图片,存在硬盘中时,如何实现一个函数,每调用一次,就会读取指定张数的图片(以n=32
为例),将其转化成矩阵,返回输出。
有 Python 基础的人可能意识到了,这里可能是使用了 Python 的 生成器 特性。其具体作用如廖雪峰博客所言:
创建一个包含100万个元素的 list,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。 所以,如果 list 元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
其关键的写法,是把传统函数的 return
换成 yield
:
def generator(samples, batch_size=32):
num_samples = len(samples)
while 1:
sklearn.utils.shuffle(samples)
for offset in range(0, num_samples, batch_size):
batch_samples = samples.iloc[offset:offset+batch_size]
images = []
angles = []
for idx in range(batch_samples.shape[0]):
name = './data/'+batch_samples.iloc[idx]['center']
center_image = cv2.cvtColor( cv2.imread(name), cv2.COLOR_BGR2RGB )
center_angle = float(batch_samples.iloc[idx]['dir'])
images.append(center_image)
angles.append(center_angle)
# trim image to only see section with road
X_train = np.array(images)
y_train = np.array(angles)
yield sklearn.utils.shuffle(X_train, y_train)
然后调用时,使用
next(generator)
即可一次返回 32 张图像以及对应的标注信息。
当然,keras
同样提供了这一模块,ImageDataGenerator,并且还是加强版,可以对图片进行 增强处理(data argument)(如旋转、反转、白化、截取等)。图片的增强处理在样本数量不多时增加样本量——因为如果图中是一只猫,旋转、反转、颜色调整之后,这张图片可能会不太相同,但它仍然是一只猫:
datagen = ImageDataGenerator(
featurewise_center=False,
samplewise_center=False,
featurewise_std_normalization=False,
samplewise_std_normalization=False,
zca_whitening=False,
width_shift_range=0.1,
height_shift_range=0.1,
horizontal_flip=True,
vertical_flip=False)
# compute quantities required for featurewise normalization
datagen.fit(X_train)
这一部分谈的是,如何使用基于批量梯度下降算法的凸优化模块,优化模型参数。
前面提到,深度学习的“梯度下降”计算,可以理解成搬走一座大山,而“批量梯度下降”,则是一群人拿着土筐,一点一点把山上的土给搬下山。那么这一点具体应该如何实现呢?其实在第二讲,我们就实现了一个随机批量梯度下降(Stochastic gradient descent, SGD),这里再回顾一下:
def sgd_update(trainables, learning_rate=1e-2):
for t in trainables:
t.value = t.value - learning_rate * t.gradients[t]
#训练神经网络的过程
for i in range(epochs):
loss = 0
for j in range(steps_per_epoch):
# 输入模块,将全部所有数据按照样本拆分成若干批次
X_batch, y_batch = resample(X_, y_, n_samples=batch_size)
# 各种深度学习零件搭建的深度神经网络
forward_and_backward(graph)
# 凸优化模块
sgd_update(trainables, 0.1)
当然,SGD 其实并不是一个很好的方法,有很多改进版本,可以用下面这张gif图概况:
Keras 里,可以直接使用 SGD, Adagrad, Adadelta, RMSProp 以及 Adam 等模块。其实在优化过程中,直接使用 Adam 默认参数,基本就可以得到最优的结果:
from keras.optimizers import Adam
adam = Adam()
model.compile(loss='categorical_crossentropy',
optimizer=adam,
metrics=['accuracy'])
最后我们用一个keras 中的官方示例,来结束本讲。
首先做一些前期准备:
# 初始化
from __future__ import print_function
import numpy as np
from keras.callbacks import TensorBoard
from keras.models import Sequential
from keras.optimizers import Adam
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPool2D
from keras.utils import np_utils
from keras import backend as K
from keras.callbacks import ModelCheckpoint
from keras.preprocessing.image import ImageDataGenerator
from keras.datasets import cifar10
from keras.backend.tensorflow_backend import set_session
import tensorflow as tf
config = tf.ConfigProto()
config.gpu_options.allow_growth=True
set_session(tf.Session(config=config))
np.random.seed(0)
print("Initialized!")
# 定义变量
batch_size = 32
nb_classes = 10
nb_epoch = 50
img_rows, img_cols = 32, 32
nb_filters = [32, 32, 64, 64]
pool_size = (2, 2)
kernel_size = (3, 3)
#
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
X_train = X_train.astype("float32") / 255
X_test = X_test.astype("float32") / 255
y_train = y_train
y_test = y_test
input_shape = (img_rows, img_cols, 3)
Y_train = np_utils.to_categorical(y_train, nb_classes)
Y_test = np_utils.to_categorical(y_test, nb_classes)
上游部分, 基于生成器的批量生成输入模块:
datagen = ImageDataGenerator(
featurewise_center=False,
samplewise_center=False,
featurewise_std_normalization=False,
samplewise_std_normalization=False,
zca_whitening=False,
rotation_range=0,
width_shift_range=0.1,
height_shift_range=0.1,
horizontal_flip=True,
vertical_flip=False)
datagen.fit(X_train)
核心部分,用各种零件搭建深度神经网络:
model = Sequential()
model.add(Conv2D(nb_filters[0], kernel_size, padding='same',input_shape=X_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(nb_filters[1], kernel_size))
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size=pool_size))
model.add(Dropout(0.25))
model.add(Conv2D(nb_filters[2], kernel_size, padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(nb_filters[3], kernel_size))
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size=pool_size))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(nb_classes))
model.add(Activation('softmax'))
构建的模型如下:
下游部分,使用凸优化模块:
adam = Adam(lr=0.0001)
model.compile(loss='categorical_crossentropy',
optimizer=adam,
metrics=['accuracy'])
最后,开始训练模型,并且评估模型准确性:
#训练模型
best_model = ModelCheckpoint("cifar10_best.h5", monitor='val_loss', verbose=0, save_best_only=True)
tb = TensorBoard(log_dir="./logs")
model.fit_generator(datagen.flow(X_train, Y_train, batch_size=batch_size),
steps_per_epoch=X_train.shape[0] // batch_size,
epochs=nb_epoch, verbose=1,
validation_data=(X_test, Y_test), callbacks=[best_model,tb])
# 模型评分
score = model.evaluate(X_test, Y_test, verbose=0)
# 输出结果
print('Test score:', score[0])
print("Accuracy: %.2f%%" % (score[1]*100))
print("Compiled!")
以上代码本人使用 Pascal TitanX 执行,50个 epoch 中,每个 epoch 用时 12s 左右,总计用时在十五分钟以内,约25 epoch 后,验证集的准确率数会逐步收敛在0.8左右。
首先你得了解什么是深度学习。
深度学习是机器学习中一种基于对数据进行表征学习的方法。观测值(例如一幅图像)可以使用多种方式来表示,如每个像素强度值的向量,或者更抽象地表示成一系列边、特定形状的区域等。而使用某些特定的表示方法更容易从实例中学习任务(例如,人脸识别或面部表情识别)。
深度学习的框架是为不同的问题提供解决方案的。主要有8类: Caffe,Theano, PyTorch, Torch ,MXnet, Chainer, Keras 和 TensorFlow。这几类都支持主流编程语言,具体内容可以上各自官网查询。
还是看哪一个框架是你需求的。腾讯云GPU服务器可以搭建深度学习环境,如果你使用腾讯GPU的话还是推荐学习TensorFlow,是Google创建的,也是最适合小白的。TensorFlow的基本介绍如下:
神经网络模型,是上一章节提到的典型的监督学习问题,即我们有一组输入以及对应的目标输出,求最优模型。通过最优模型,当我们有新的输入时,可以得到一个近似真实的预测输出。
我们先看一下如何实现这样一个简单的神经网络:
x = [1,2,3]
,y = [-0.85, 0.72]
求所需参数 w1_0
w2_0
b1_0
b2_0
, 使得给定输入 x 下得到的输出 ,和目标输出
之间的平均均方误差 (Mean Square Errors, MSE) 最小化 。
我们首先需要思考,有几个参数?由于是两层神经网络,结构如下图(图片来源http://stackoverflow.com/questions/22054877/backpropagation-training-stuck), 其中输入层为 3,中间层为 4,输出层是 2:
因此,其中总共包含 (3x4+4) + (4*2+2) = 26 个参数需要训练。我们可以如图初始化参数。参数可以随机初始化,也可以随便指定:
# python
import numpy as np
w1_0 = np.array([[ 0.1, 0.2, 0.3, 0.4],
[ 0.5, 0.6, 0.7, 0.8],
[ 0.9, 1.0, 1.1, 1.2]])
w2_0 = np.array([[ 1.3, 1.4],
[ 1.5, 1.6],
[ 1.7, 1.8],
[ 1.9, 2.0]])
b1_0 = np.array( [-2.0, -6.0, -1.0, -7.0])
b2_0 = np.array( [-2.5, -5.0])
我们进行一次正向传播:
# python
x = [1,2,3]
y = [-0.85, 0.72]
o1 = np.dot(x, w1_0 ) + b1_0
os1 = np.power(1+np.exp(o1*-1), -1)
o2 = np.dot(os1, w2_0) + b2_0
os2 = np.tanh(o2)
再进行一次反向传播:
# python
alpha = 0.1
grad_os2 = (y - os2) * (1-np.power(os2, 2))
grad_os1 = np.dot(w2_0, grad_os2.T).T * (1-os1)*os1
grad_w2 = ...
grad_b2 = ...
...
...
w2_0 = w2_0 + alpha * grad_w2
b2_0 = b2_0 + alpha * grad_b2
...
...
如此反复多次,直到最终误差收敛。进行反向传播时,需要将所有参数的求导结果都写上去,然后根据求导结果更新参数。我这里就没有写全,因为一层一层推导实在是太过麻烦。更重要的是,当我们需要训练新的神经网络结构时,这些都需要重新推导一次,费时费力。
然而仔细想一想,这个推导的过程也并非无规律可循。即上一级的神经网络梯度输出,会被用作下一级计算梯度的输入,同时下一级计算梯度的输出,会被作为上一级神经网络的输入。于是我们就思考能否将这一过程抽象化,做成一个可以自动求导的框架?OK,以 Tensorflow 为代表的一系列深度学习框架,正是根据这一思路诞生的。
近几年最火的深度学习框架是什么?毫无疑问,Tensorflow 高票当选。
但实际上,这些深度学习框架都具有一些普遍特征。Gokula Krishnan Santhanam认为,大部分深度学习框架都包含以下五个核心组件:
其中,张量 Tensor 可以理解为任意维度的数组——比如一维数组被称作向量(Vector),二维的被称作矩阵(Matrix),这些都属于张量。有了张量,就有对应的基本操作,如取某行某列的值,张量乘以常数等。运用拓展包其实就相当于使用底层计算软件加速运算。
我们今天重点介绍的,就是计算图模型,以及自动微分两部分。首先介绍以 Torch 框架为例,谈谈如何实现自动求导,然后再用最简单的方法,实现这两部分。
诸如 Tensorflow 这样的深度学习框架的入门,网上有大量的 几行代码、几分钟入门这样的资料,可以快速实现手写数字识别等简单任务。但如果想深入了解 Tensorflow 的背后原理,可能就不是这么容易的事情了。这里我们简单的谈一谈这一部分。
我们知道,当我们拿到数据、训练神经网络时,网络中的所有参数都是 变量。训练模型的过程,就是如何得到一组最佳变量,使预测最准确的过程。这个过程实际上就是,输入数据经过 正向传播,变成预测,然后预测与实际情况的误差 反向传播 误差回来,更新变量。如此反复多次,得到最优的参数。这里就会遇到一个问题,神经网络这么多层,如何保证正向、反向传播都可以正确运行?
值得思考的是,这两种传播方式,都具有 管道传播 的特征。正向传播一层一层算就可以了,上一层网络的结果作为下一层的输入。而反向传播过程可以利用 链式求导法则,从后往前,不断将误差分摊到每一个参数的头上。
图片来源:Colah博客
进过抽象化后,我们发现,深度学习框架中的 每一个模块都需要两个函数,一个连接正向,一个连接反向。这里的正向和反向,如同武侠小说中的 任督二脉。而训练模型的过程,数据通过正向传播生成预测结果,进而将误差反向传回更新参数,就如同让真气通过任督二脉在体内游走,随着训练误差逐渐缩小收敛,深度神经网络也将打通任督二脉。
接下来,我们将首先审视一下 Torch 框架的源码如何实现这两部分内容,其次我们通过 Python 直接编写一个最简单的深度学习框架。
举 Torch 的 nn 项目的例子是因为Torch 的代码文件结构比较简单,Tensorflow 的规律和Torch比较近似,但文件结构相对更加复杂,有兴趣的可以仔细读读相关文章。
Torch nn 模块Github 源码 这个目录下的几乎所有 .lua 文件,都有这两个函数:
# lua
function xxx:updateOutput(input)
input.THNN.xxx_updateOutput(
input:cdata(),
self.output:cdata()
)
return self.output
end
function xxx:updateGradInput(input, gradOutput)
input.THNN.xxx_updateGradInput(
input:cdata(),
gradOutput:cdata(),
self.gradInput:cdata(),
self.output:cdata()
)
return self.gradInput
end
这里其实是相当于留了两个方法的定义,没有写具体功能。具体功能的代码,在 ./lib/THNN/generic 目录中用 C 实现实现,具体以 Sigmoid 函数举例。
我们知道 Sigmoid 函数的形式是:
代码实现起来是这样:
# lua
void THNN_(Sigmoid_updateOutput)( THNNState
*state, THTensor
*input, THTensor
*output)
{
THTensor_(resizeAs)(output, input);
TH_TENSOR_APPLY2(real, output, real, input,
*output_data = 1./(1.+ exp(- *input_data));
);
}
Sigmoid 函数求导变成:
所以这里在实现的时候就是:
// c
void THNN_(Sigmoid_updateGradInput)(
THNNState *state,
THTensor *input,
THTensor *gradOutput,
THTensor *gradInput,
THTensor *output)
{
THNN_CHECK_NELEMENT(input, gradOutput);
THTensor_(resizeAs)(gradInput, output);
TH_TENSOR_APPLY3(real, gradInput, real, gradOutput, real, output,
real z = * output_data;
*gradInput_data = *gradOutput_data * (1. - z) * z;
);
}
大家应该注意到了一点, updateOutput
函数, output_data 在等号左边, input_data 在等号右边。 而 updateGradInput
函数, gradInput_data 在等号左边, gradOutput_data 在等号右边。 这里,output = f(input)
对应的是 正向传播 input = f(output)
对应的是 反向传播。
这部分内容属于“造轮子”,并且借用了优达学城的一个小型项目 MiniFlow。
首先,我们实现一个父类 Node
,然后基于这个父类,依次实现 Input Linear Sigmoid 等模块。这里运用了简单的 Python Class 继承。这些模块中,需要将 forward
和 backward
两个方法针对每个模块分别重写。
代码如下:
# python
class Node(object):
"""
Base class for nodes in the network.
Arguments:
`inbound_nodes`: A list of nodes with edges into this node.
"""
def __init__(self, inbound_nodes=[]):
"""
Node's constructor (runs when the object is instantiated). Sets
properties that all nodes need.
"""
# A list of nodes with edges into this node.
self.inbound_nodes = inbound_nodes
# The eventual value of this node. Set by running
# the forward() method.
self.value = None
# A list of nodes that this node outputs to.
self.outbound_nodes = []
# New property! Keys are the inputs to this node and
# their values are the partials of this node with
# respect to that input.
self.gradients = {}
# Sets this node as an outbound node for all of
# this node's inputs.
for node in inbound_nodes:
node.outbound_nodes.append(self)
def forward(self):
"""
Every node that uses this class as a base class will
need to define its own `forward` method.
"""
raise NotImplementedError
def backward(self):
"""
Every node that uses this class as a base class will
need to define its own `backward` method.
"""
raise NotImplementedError
class Input(Node):
"""
A generic input into the network.
"""
def __init__(self):
Node.__init__(self)
def forward(self):
pass
def backward(self):
self.gradients = {self: 0}
for n in self.outbound_nodes:
self.gradients[self] += n.gradients[self]
class Linear(Node):
"""
Represents a node that performs a linear transform.
"""
def __init__(self, X, W, b):
Node.__init__(self, [X, W, b])
def forward(self):
"""
Performs the math behind a linear transform.
"""
X = self.inbound_nodes[0].value
W = self.inbound_nodes[1].value
b = self.inbound_nodes[2].value
self.value = np.dot(X, W) + b
def backward(self):
"""
Calculates the gradient based on the output values.
"""
self.gradients = {n: np.zeros_like(n.value) for n in self.inbound_nodes}
for n in self.outbound_nodes:
grad_cost = n.gradients[self]
self.gradients[self.inbound_nodes[0]] += np.dot(grad_cost, self.inbound_nodes[1].value.T)
self.gradients[self.inbound_nodes[1]] += np.dot(self.inbound_nodes[0].value.T, grad_cost)
self.gradients[self.inbound_nodes[2]] += np.sum(grad_cost, axis=0, keepdims=False)
class Sigmoid(Node):
"""
Represents a node that performs the sigmoid activation function.
"""
def __init__(self, node):
Node.__init__(self, [node])
def _sigmoid(self, x):
"""
This method is separate from `forward` because it
will be used with `backward` as well.
`x`: A numpy array-like object.
"""
return 1. / (1. + np.exp(-x))
def forward(self):
"""
Perform the sigmoid function and set the value.
"""
input_value = self.inbound_nodes[0].value
self.value = self._sigmoid(input_value)
def backward(self):
"""
Calculates the gradient using the derivative of
the sigmoid function.
"""
self.gradients = {n: np.zeros_like(n.value) for n in self.inbound_nodes}
for n in self.outbound_nodes:
grad_cost = n.gradients[self]
sigmoid = self.value
self.gradients[self.inbound_nodes[0]] += sigmoid * (1 - sigmoid) * grad_cost
class Tanh(Node):
def __init__(self, node):
"""
The tanh cost function.
Should be used as the last node for a network.
"""
Node.__init__(self, [node])
def forward(self):
"""
Calculates the tanh.
"""
input_value = self.inbound_nodes[0].value
self.value = np.tanh(input_value)
def backward(self):
"""
Calculates the gradient of the cost.
"""
self.gradients = {n: np.zeros_like(n.value) for n in self.inbound_nodes}
for n in self.outbound_nodes:
grad_cost = n.gradients[self]
tanh = self.value
self.gradients[self.inbound_nodes[0]] += (1 + tanh) * (1 - tanh) * grad_cost.T
class MSE(Node):
def __init__(self, y, a):
"""
The mean squared error cost function.
Should be used as the last node for a network.
"""
Node.__init__(self, [y, a])
def forward(self):
"""
Calculates the mean squared error.
"""
y = self.inbound_nodes[0].value.reshape(-1, 1)
a = self.inbound_nodes[1].value.reshape(-1, 1)
self.m = self.inbound_nodes[0].value.shape[0]
self.diff = y - a
self.value = np.mean(self.diff**2)
def backward(self):
"""
Calculates the gradient of the cost.
"""
self.gradients[self.inbound_nodes[0]] = (2 / self.m) * self.diff
self.gradients[self.inbound_nodes[1]] = (-2 / self.m) * self.diff
优化部分则会在以后的系列中单独详细说明。这里主要将简单讲一下图计算的算法调度。就是实际上Tensorflow的各个模块会生成一个有向无环图,如下图(来源http://www.geeksforgeeks.org/topological-sorting-indegree-based-solution/):
在计算过程中,几个模块存在着相互依赖关系,比如要计算模块1,就必须完成模块3和模块4,而要完成模块3,就需要在之前顺次完成模块5、2;因此这里可以使用 Kahn 算法作为调度算法(下面的 topological_sort
函数),从计算图中,推导出类似 5->2->3->4->1 的计算顺序。
# python
def topological_sort(feed_dict):
"""
Sort the nodes in topological order using Kahn's Algorithm.
`feed_dict`: A dictionary where the key is a `Input` Node and the value is the respective value feed to that Node.
Returns a list of sorted nodes.
"""
input_nodes = [n for n in feed_dict.keys()]
G = {}
nodes = [n for n in input_nodes]
while len(nodes) > 0:
n = nodes.pop(0)
if n not in G:
G[n] = {'in': set(), 'out': set()}
for m in n.outbound_nodes:
if m not in G:
G[m] = {'in': set(), 'out': set()}
G[n]['out'].add(m)
G[m]['in'].add(n)
nodes.append(m)
L = []
S = set(input_nodes)
while len(S) > 0:
n = S.pop()
if isinstance(n, Input):
n.value = feed_dict[n]
L.append(n)
for m in n.outbound_nodes:
G[n]['out'].remove(m)
G[m]['in'].remove(n)
if len(G[m]['in']) == 0:
S.add(m)
return L
def forward_and_backward(graph):
"""
Performs a forward pass and a backward pass through a list of sorted Nodes.
Arguments:
`graph`: The result of calling `topological_sort`.
"""
for n in graph:
n.forward()
for n in graph[::-1]:
n.backward()
def sgd_update(trainables, learning_rate=1e-2):
"""
Updates the value of each trainable with SGD.
Arguments:
`trainables`: A list of `Input` Nodes representing weights/biases.
`learning_rate`: The learning rate.
"""
for t in trainables:
t.value = t.value - learning_rate * t.gradients[t]
# python
import numpy as np
from sklearn.utils import resample
np.random.seed(0)
w1_0 = np.array([[ 0.1, 0.2, 0.3, 0.4],
[ 0.5, 0.6, 0.7, 0.8],
[ 0.9, 1.0, 1.1, 1.2]])
w2_0 = np.array([[ 1.3, 1.4],
[ 1.5, 1.6],
[ 1.7, 1.8],
[ 1.9, 2.0]])
b1_0 = np.array( [-2.0, -6.0, -1.0, -7.0])
b2_0 = np.array( [-2.5, -5.0])
X_ = np.array([[1.0, 2.0, 3.0]])
y_ = np.array([[-0.85, 0.75]])
n_features = X_.shape[1]
W1_ = w1_0
b1_ = b1_0
W2_ = w2_0
b2_ = b2_0
X, y = Input(), Input()
W1, b1 = Input(), Input()
W2, b2 = Input(), Input()
l1 = Linear(X, W1, b1)
s1 = Sigmoid(l1)
l2 = Linear(s1, W2, b2)
t1 = Tanh(l2)
cost = MSE(y, t1)
feed_dict = {
X: X_, y: y_,
W1: W1_, b1: b1_,
W2: W2_, b2: b2_
}
epochs = 10
m = X_.shape[0]
batch_size = 1
steps_per_epoch = m // batch_size
graph = topological_sort(feed_dict)
trainables = [W1, b1, W2, b2]
l_Mat_W1 = [w1_0]
l_Mat_W2 = [w2_0]
l_Mat_out = []
l_val = []
for i in range(epochs):
loss = 0
for j in range(steps_per_epoch):
X_batch, y_batch = resample(X_, y_, n_samples=batch_size)
X.value = X_batch
y.value = y_batch
forward_and_backward(graph)
sgd_update(trainables, 0.1)
loss += graph[-1].value
mat_W1 = []
mat_W2 = []
for i in graph:
try:
if (i.value.shape[0] == 3) and (i.value.shape[1] == 4):
mat_W1 = i.value
if (i.value.shape[0] == 4) and (i.value.shape[1] == 2):
mat_W2 = i.value
except:
pass
l_Mat_W1.append(mat_W1)
l_Mat_W2.append(mat_W2)
l_Mat_out.append(graph[9].value)
来观察一下。当然还有更高级的可视化方法:https://jizhi.im/blog/post/v_nn_learn
# python
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure( figsize=(14,10))
ax0 = fig.add_subplot(131)
#aax0 = fig.add_axes([0, 0, 0.3, 0.1])
c0 = ax0.imshow(np.array(l_Mat_out).reshape([-1,2]).T, interpolation='nearest',aspect='auto', cmap="Reds", vmax=1, vmin=-1)
ax0.set_title("Output")
cbar = fig.colorbar(c0, ticks=[-1, 0, 1])
ax1 = fig.add_subplot(132)
c1 = ax1.imshow(np.array(l_Mat_W1).reshape(len(l_Mat_W1), 12).T, interpolation='nearest',aspect='auto', cmap="Reds")
ax1.set_title("w1")
cbar = fig.colorbar(c1, ticks=[np.min(np.array(l_Mat_W1)), np.max(np.array(l_Mat_W1))])
ax2 = fig.add_subplot(133)
c2 = ax2.imshow(np.array(l_Mat_W2).reshape(len(l_Mat_W2), 8).T, interpolation='nearest',aspect='auto', cmap="Reds")
ax2.set_title("w2")
cbar = fig.colorbar(c2, ticks=[np.min(np.array(l_Mat_W2)), np.max(np.array(l_Mat_W2))])
ax0.set_yticks([0,1])
ax0.set_yticklabels(["out0", "out1"])
ax1.set_xlabel("epochs")
#for i in range(len(l_Mat_W1)):
我们注意到,随着训练轮数 Epoch 不断增多, Output 值从最初的 [0.72, -0.88]
不断接近 y = [-0.85, 0.72]
, 其背后的原因,是模型参数不断的从初始化的值变化、更新,如图中的 w1 w2 两个矩阵。
好了,最简单的轮子已经造好了。 我们的轮子,实现了 Input
Linear
Sigmoid
Tanh
以及 MSE
这几个模块。
相似问题