年度归档:2018年

21.1 TensorFlow.js主要功能

TensorFlow.js是一个JavaScript库,它可以将机器学习功能添加到任何Web应用程序中。使用TensorFlow.js,可以从头开发机器学习脚本。你可以使用API在浏览器或Node.js服务器应用程序中构建和训练模型。并且,你可以使用TensorFlow.js在JavaScript环境中运行现有模型。
甚至,你可以使用TensorFlow.js用自己的数据再训练预先存在的机器学习模型,这些其中包括浏览器中客户端可用的数据。例如,你可以使用网络摄像头中的图像数据。如果你是一名机器学习、深度学习爱好者,那么TensorFlow.js是学习的好方法!
TensorFlow.js利用 WebGL 来进行加速的机器学习类库,它基于浏览器,提供了高层次的 JavaScript API 接口。它将高性能机器学习构建块带到您的指尖,使您能够在浏览器中训练神经网络或在推理模式下运行预先训练的模型。
TensorFlow.js的主要功能包括:利用用js开发机器学习、运行已有模型、重新训练已有模型等,具体请看下图:

有关安装/配置 TensorFlow.js 的指南,请参阅:https://js.tensorflow.org/index.html#getting-started。

21.2 安装Node.js和NPM

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。 Node.js 的使用包管理器 npm来管理所有模块的安装、配置、删除等操作,使用起来非常方便。但是想要配置好npm的使用环境还是稍微有点复杂,下面跟着我一起来学习在windows系统上配置NodeJS和NPM吧。
具体安装大家可参考:
https://jingyan.baidu.com/article/48b37f8dd141b41a646488bc.html

21.3 Javascript(js)简介

1.JavaScript历史
要了解JavaScript,我们首先要回顾一下JavaScript的诞生。
在上个世纪的1995年,当时的网景公司正凭借其Navigator浏览器成为Web时代开启时最著名的第一代互联网公司。
由于网景公司希望能在静态HTML页面上添加一些动态效果,于是叫Brendan Eich这哥们在两周之内设计出了JavaScript语言。你没看错,这哥们只用了10天时间。
为什么起名叫JavaScript?原因是当时Java语言非常红火,所以网景公司希望借Java的名气来推广,但事实上JavaScript除了语法上有点像Java,其他部分基本上没啥关系。
2.ECMAScript
因为网景开发了JavaScript,一年后微软又模仿JavaScript开发了JScript,为了让JavaScript成为全球标准,几个公司联合ECMA(European Computer Manufacturers Association)组织定制了JavaScript语言的标准,被称为ECMAScript标准。
所以简单说来就是,ECMAScript是一种语言标准,而JavaScript是网景公司对ECMAScript标准的一种实现。
那为什么不直接把JavaScript定为标准呢?因为JavaScript是网景的注册商标。
不过大多数时候,我们还是用JavaScript这个词。如果你遇到ECMAScript这个词,简单把它替换为JavaScript就行了。
3.JavaScript版本
JavaScript的标准——ECMAScript在不断发展,最新版ECMAScript 6标准(简称ES6)已经在2015年6月正式发布了,所以,讲到JavaScript的版本,实际上就是说它实现了ECMAScript标准的哪个版本。想更详细了解JavaScript,大家可参考:
http://www.runoob.com/js/js-howto.html

21.4 TensorFlow.JS基础

21.4.1 主要概念

TensorFlow.js的主要概念,包括张量(Tensor)、变量(Variables)、操作(operations)、模型和层(models and layers)等,如下图所示。

21.4.2 张量

张量(Tensor)是TensorFlow中的主要数据单位。张量包含一组数值,可以是任何形状:一维或多维。当你创建新的张量时,你还需要定义形状(shape)。你可以通过使用tensor函数并传入第二个参数来定义形状,如下所示:

这是定义具有两行四列形状的张量。产生的张量如下所示

也可以让TensorFlow推断出张量的形状,如下列代码。

我们可以使用input.shape来检索张量的大小。

这里的形状为[2]。我们还可以创建具有特定大小的张量。例如,下面我们创建一个形状为[2,2]的零值张量。

这行代码创建了以下张量
[[0,0,0],
[0,0,0]]
此外,你可以使用以下函数来增强代码可读性:

tf.scalar:只有一个值的张量
tf.tensor1d:具有一个维度的张量
tf.tensor2d:具有两个维度的张量
tf.tensor3d:具有三维的张量
tf.tensor4d:具有四个维度的张量,
下图为常见的几种张量示意图:

如表示标量:

如表示二维张量:

在TensorFlow.js中,所有张量都是不可变的。这意味着张量一旦创建,之后就无法改变。如果你执行一个更改量值的操作,总是会创建一个新的张量并返回结果值。

21.4.3 变量

张量(Tensors) 是不可变的,一旦创建,不能改变其值;而变量(variables) 则可以动态改变其值,主要用于在模型训练期间存储和更新值。您可以使用assign方法为现有变量指定新值:

变量主要用于在模型训练期间存储然后更新参数值。

21.4.4 操作

通过使用TensorFlow操作,你可以操纵张量的数据。由于张量运算的不变性,结果值总是返回一个新的张量。
TensorFlow.js提供了许多有用的操作,如square,add,sub和mul。你可以直接应用操作,如下所示:。

执行此代码后,新张量包含以下值:
[[1, 4 ],
[9, 16]]

21.4.5 内存管理

由于TensorFlow.js使用GPU来加速数学运算,因此在使用张量和变量时需要管理GPU内存。
TensorFlow.js提供了两个函数来帮助解决这个问题:dispose和tf.tidy。
 dispose
我们可以在张量或变量上调用dispose来清除它并释放其GPU内存:

在进行大量张量操作时,使用dispose会很麻烦。 TensorFlow.js提供了另一个函数tf.tidy,它与JavaScript中的常规作用域起着类似的作用,但是对于GPU支持的张量。
tf.tidy执行一个函数并清除所创建的任何中间张量,释放它们的GPU内存。 它不会清除内部函数的返回值。

使用tf.tidy将有助于防止应用程序中的内存泄漏。 它还可以用于更加谨慎地控制何时回收内存。
【注意】
 传递给tf.tidy的函数应该是同步的,也不会返回Promise。 我们建议保留更新UI的代码或在tf.tidy之外发出远程请求。
 tf.tidy不会清理变量。 变量通常持续到机器学习模型的整个生命周期,因此TensorFlow.js即使它们是在tf.tidy的情况下创建的,也不会清理它们。 但是,您可以手动调用dispose来清理。

21.4.6 模型和层

从概念上讲,模型是一种函数,给定一些输入将产生一些所需的输出。在TensorFlow.js中,有两种方法可以创建模型。 您可以直接使用ops来表示模型所做的事情。 例如:

我们也可以使用高级API tf.model来构建层中的模型,这是深度学习中的流行方法。 以下代码构造了一个tf.sequential模型:

其中units. 激活输出的数量。由于这是最后一层,这里涉及20个类别的分类任务。units的数据量指节点数。
TensorFlow.js中有许多不同类型的层。 如tf.layers.simpleRNN,tf.layers.gru和tf.layers.lstm等。
【注意】
利用TensorFlow.js构建网络时,第一层必须明确指定输入形状,其余的层默认从前面的层输入。如下示例代码:
(1)首先,用tf.sequential()实例化构建模型model
将使用Sequential模型(最简单的模型类型),其中张量将连续地从一层传递到下一层。

(2)添加一个卷积层

 nputShape.将流入模型第一层的数据的形状。这里,我们的MNIST样本是28x28像素的黑白图像。图像数据的规范格式是[row,column,depth],所以我们在这里配置的形状是[28,28,1]——每个维度有28rowX28column个像素,而depth为1是因为我们的图像只有1个颜色通道。
 kernelSize. 应用于输入数据的滑动卷积滤波器窗口的大小。在这里,我们设置kernelSize为5,它表示一个5x5的正方形卷积窗口。
 filters. 应用于输入数据,大小为kernelSize的滤波器窗口的数量。在这里,我们将对数据应用8个过滤器。
 strides. 滑动窗口的“步长” - 即每次在图像上移动时,滤波器将移动多少个像素。在这里,我们指定步幅为1,这意味着过滤器将以1像素为单位滑过图像。
 activation.卷积完成后应用于数据的 激活函数。这里,我们使用了 Rectified Linear Unit (ReLU)函数,这是ML模型中非常常见的激活函数。
 kernelInitializer. 用于随机初始化模型权重的方法,这对于训练动态是非常重要的。我们不会详细介绍初始化的细节,这里VarianceScaling是一个很不错的初始化器。

(2)添加一个池化层

 poolSize. 应用于输入数据的滑动窗口大小。在这里,我们设置poolSize为[2,2],这意味着池化层将对输入数据应用2x2窗口。
 stride. 滑动窗口的“步长” - 即每次在输入数据上移动时,窗口将移动多少个像素。在这里,我们指定[2,2]的步长,这意味着滤波器将在水平和垂直两个方向上以2个像素为单位滑过图像。
【注意】
由于poolSize和strides都是2×2,所以池窗口将完全不重叠。这意味着池化层会将前一层的激活图的大小减半。
(3)再添加一个卷积层
重复使用层结构是神经网络中的常见模式。我们添加第二个卷积层到模型,并在其后添加池化层。请注意,在我们的第二个卷积层中,我们将滤波器数量从8增加到16。还要注意,我们没有指定inputShape,因为它可以从前一层的输出形状中推断出来。

(4)添加一个展平层
我们添加一个 flatten层,将前一层的输出平铺到一个向量中。

【注意】
展平层,既没有说明输入形状,也没有说明输出形状,这些形状都是从前层输出自动获取。
(5)输出层
最后,让我们添加一个 dense层(也称为全连接层),它将执行最终的分类。 在dense层前先对卷积+池化层的输出执行flatten也是神经网络中的另一种常见模式

 units. 激活输出的数量。由于这是最后一层,我们正在做10个类别的分类任务(数字0-9),因此我们在这里使用10个units。 (有时units被称为神经元的数量)
 kernelInitializer. 我们将对dense层使用与卷积层相同的VarianceScaling初始化策略。
 activation. 分类任务的最后一层的激活函数通常是 softmax。 Softmax将我们的10维输出向量归一化为概率分布,使得我们10个类中的每个都有一个概率值。

21.4.7 优化问题

这一部分,我们将学习如何解决优化问题。给定函数f(x),我们要求求得x=a使得f(x)最小化。为此,我们需要一个优化器。优化器是一种沿着梯度来最小化函数的算法。文献中有许多优化器,如SGD,Adam等等,这些优化器的速度和准确性各不相同。Tensorflowjs支持大多数重要的优化器。
我们将举一个简单的例子:f(x)=x⁶+2x⁴+3x²+x+1。函数的曲线图如下所示。可以看到函数的最小值在区间[-0.5,0]。我们将使用优化器来找出确切的值。

首先,我们定义要最小化的函数:

现在我们可以迭代地最小化函数以找到最小值。我们将以a=2的初始值开始,学习率定义了达到最小值的速度。我们将使用Adam优化器:

使用值为0.9的学习速率,我们发现200次迭代后的最小值对应的y为-0.16092407703399658。

更多内容可参考:
https://js.tensorflow.org/tutorials/core-concepts.html

21.5设置项目(使用npm)

(1)在第一步中,我们需要设置项目。创建一个新的空目录。

(2)切换到新创建的项目文件夹

以下操作,都在该文件夹下
(3)创建一个package.json文件
在文件夹中,我们现在准备创建一个package.json文件,以便我们能够通过使用Node.js包管理器来管理依赖项:

(4)安装Parcel捆绑器
因为我们将在项目文件夹中本地安装依赖项(例如Tensorflow.js库),所以我们需要为Web应用程序使用模块捆绑器(bundler)。为了尽可能简单,我们将使用Parcel Web应用程序捆绑器,因为Parcel不需要进行配置。让我们通过在项目目录中执行以下命令来安装Parcel捆绑器:

(5)创建两个空文件
接下来,让我们为我们的实现创建两个新的空文件:

(6)安装Bootstrap库
我们将Bootstrap库添加为依赖项,因为我们将为我们的用户界面元素使用一些Bootstrap CSS类:

(7)修改两个空文件
在index.html中,让我们插入以下基本html页面的代码:

另外,将以下代码添加到index.js

我们将文本Hello World写入具有ID输出的元素,以在屏幕上查看第一个结果并获得正确处理JS代码的确认。

(8)启动程序及web服务
最后,让我们通过使用parcel命令启动构建程序和开发的Web服务:

你现在应该可以在浏览器中通过URL http://localhost:1234打开网站。结果应与你在以下截图中看到的内容对应:

【注意】
以上步骤我们也可用yarn执行,npm与yarn的对应关系及优缺点,可参考:
https://juejin.im/entry/5a73ca7d6fb9a063435ea9ad
http://www.fly63.com/article/detial/554

21.6设置项目(使用yarn)

(1)在第一步中,我们需要设置项目。创建一个新的空目录。

(2)切换到新创建的项目文件夹

以下操作,都在该文件夹下
(3)创建一个package.json文件
在文件夹中,我们现在准备创建一个package.json文件,以便我们能够通过使用Node.js包管理器来管理依赖项:

(4)安装Parcel捆绑器
Parcel 是一个 web 应用打包工具, 与其他工具的区别在于开发者的使用体验。它利用多核处理器提供了极快的速度, 并且不需要任何配置。
因为我们将在项目文件夹中本地安装依赖项(例如Tensorflow.js库),所以我们需要为Web应用程序使用模块捆绑器(bundler)。为了尽可能简单,我们将使用Parcel Web应用程序捆绑器,因为Parcel不需要进行配置。让我们通过在项目目录中执行以下命令来安装Parcel捆绑器:

(5)创建两个空文件
接下来,让我们为我们的实现创建两个新的空文件:

(6)安装Bootstrap库
我们将Bootstrap库添加为依赖项,因为我们将为我们的用户界面元素使用一些Bootstrap CSS类:

将生成一个文件(yarn.lock)和一个文件夹(node_modules)
(7)修改两个空文件
在index.html中,让我们插入以下基本html页面的代码:

另外,将以下代码添加到index.js

我们将文本Hello World写入具有ID输出的元素,以在屏幕上查看第一个结果并获得正确处理JS代码的确认。

(8)启动程序及web服务
最后,让我们通过使用parcel命令启动构建程序和开发的Web服务:

执行该命令,将生成一个dist文件夹,同时更新相关文件。
你现在应该可以在浏览器中通过URL http://localhost:1234打开网站。当文件改变时它仍然会自动重建并支持热替换。结果应与你在以下截图中看到的内容对应:

【说明】

21.7实例详解

本实例利用tensorflow.js定义一个模型,该模型模拟一条直线(y=2x-1),然后,根据训练好的模型,在浏览器上,输入一个值,实时预测值,新体验,很不错哦!
(1)添加ensorflow.js
为了Tensorflow.js添加到项目中,我们再次使用NPM并在项目目录中执行以下命令

这将下载并将其安装到node_modules文件夹中。成功执行此命令后,我们现在可以通过在文件顶部添加以下import语句来导入index.js中的Tensorflow.js库:

当我们将TensorFlow.js导入为tf后,我们就可以通过在代码中使用tf对象来访问TensorFlow.js API。
(2)定义模型
现在TensorFlow.js已经可用,让我们从一个简单的机器学习练习开始。下面的示例应用程序涵盖的机器学习脚本是公式Y = 2X-1,这是个线性回归。
此函数返回给定X对应的Y值。如果绘制点(X,Y),你将得到一条直线,如下所示:

接下来我们将使用来自该函数的输入数据(X,Y)并使用这些数字训练模型。然后使用训练好的模型,根据新的X值来预测Y值。期望从模型返回的Y结果接近函数返回的精确值。

让我们创建一个非常简单的神经网络来实现。此模型只需处理一个输入值和一个输出值:

首先,我们通过调用tf.sequential方法创建一个新的模型实例。得到一个新的序列模型。其中一层的输出是下一层的输入,即模型是层的简单“堆叠”,没有分支或跳过。
创建好模型后,我们准备通过调用model.add来添加第一层。通过调用tf.layers.dense将新层传递给add方法。这会创建一个稠密层或全连接层。在稠密层中,层中的每个节点都连接到前一层中的每个节点。对于我们的示例,只需向神经网络添加一个具有一个输入和输出形状的密集层就足够了。

在下一步中,我们需要为模型指定损失函数和优化函数。

通过将配置对象传递给模型实例的编译方法来完成。配置对象包含两个属性:
 loss:这里我们使用meanSquaredError损失函数。通常,损失函数用于将一个或多个变量的值映射到表示与该值相关联的一些“损失”的实数上。如果训练模型,它会尝试最小化损失函数的结果。估计量的均方误差是误差平方的平均值 - 即估计值与估计值之间的平均平方差。
 optimizer:要使用的优化器函数。我们的线性回归机器学习任务使用的是sgd函数。Sgd代表Stochastic Gradient Descent,它是一个适用于线性回归任务的优化器函数。
现在模型已配置完成,接下来将训练模型。
(3)训练模型
为了用函数Y=2X-1的值训练模型,我们定义了两个形状为6,1的张量。第一张量xs包含x值,第二张量ys包含相应的y值:

把这两个张量传递给调用的model.fit方法来训练模型

对于第三个参数,我们传递一个对象,该对象包含一个名为epochs的属性,该属性设置为值500。此处指定的数字是指定TensorFlow.js通过训练集的次数。
fit方法的结果是一个Promise,所以我们注册一个回调函数,该函数在训练结束时被激活。
(4)预测
现在让我们在这个回调函数中执行最后一步,并根据给定的x值预测y值

使用model.predict方法完成预测。该方法以张量的形式接收输入值作为参数。在这个特定情况下,我们在内部创建一个只有一个值(5)的张量并将其传递给预测。通过调用print函数,我们确保将结果值打印到控制台,如下所示:

输出显示预测值为8.9962864并且非常接近9(如果x设置为5,函数Y=2X-1的Y值为9)
(5)优化界面
已上面经实现的示例是使用固定输入值进行预测(5)并将结果输出到浏览器控制台。让我们引入一个更复杂的用户界面,让用户能够输入用于预测的值。在index.html中添加以下代码:

这里我们使用各种Bootstrap CSS类,向页面添加输入和按钮元素,并定义用于输出结果的区域。

我们还需要在index.js中做一些更改:

注册了预测按钮的click事件的事件处理程序。在此函数内部,读取input元素的值(通过number函数把输入值转换为数据类型)并调用model.predict方法。此方法返回的结果将插入具有id输出的元素中。

现在的结果应该如下所示:

现在我们根据训练好的模型,输入值(x),就可实时预测Y值了!。单击“ Predict ”按钮完成预测。结果会直接显示在网站上。

(6)查看运行情况
通过管理平台(console),查看运行情况。

参考文档:
https://js.tensorflow.org/
https://cloud.tencent.com/developer/article/1346798

20.1 Pytorch简介

PyTorch 是 Torch 在 Python 上的衍生. 因为 Torch 是一个使用 Lua 语言的神经网络库,由于 PyTorch 采用了动态计算图(dynamic computational graph)结构,PyTorch 有一种独特的神经网络构建方法:使用和重放 tape recorder。而不是大多数开源框架,比如 TensorFlow、Caffe、CNTK、Theano 等采用的静态计算图。 使用 PyTorch,通过一种我们称之为「Reverse-mode auto-differentiation(反向模式自动微分)」的技术,你可以零延迟或零成本地任意改变你的网络的行为。
torch 产生的 tensor 放在 GPU 中加速运算 (前提是你有合适的 GPU), 就像 Numpy 会把 array 放在 CPU 中加速运。
torch是一个支持 GPU 的 Tensor 库,如果你使用 numpy,那么你就使用过 Tensor(即 ndarray)。PyTorch 提供了支持 CPU 和 GPU 的 Tensor。
pytorch版本变化

从 2015 年谷歌开源 TensorFlow 开始,深度学习框架之争越来越越激烈,全球多个看重 AI 研究与应用的科技巨头均在加大这方面的投入。从 2017 年年初发布以来,PyTorch 可谓是异军突起,短短时间内取得了一系列成果,成为了其中的明星框架。
PyTorch 1.0 预览版已出,稳定版发布在即,全新的版本融合了 Caffe2 和 ONNX 支持模块化、面向生产的功能,并保留了 PyTorch 现有的灵活、以研究为中心的设计。PyTorch 1.0 从 Caffe2 和 ONNX 移植了模块化和产品导向的功能,并将它们和 PyTorch 已有的灵活、专注研究的设计结合,已提供多种 AI 项目的从研究原型制作到产品部署的快速、无缝路径。利用 PyTorch 1.0,AI 开发者可以通过混合前端快速地实验和优化性能,该前端可以在命令式执行和声明式执行之间无缝地转换。PyTorch 1.0 中的技术已经让很多 Facebook 的产品和服务变得更强大,包括每天执行 60 亿次文本翻译。
pytorch的组成:
PyTorch由4个主要包装组成:
①.torch:类似于Numpy的通用数组库,可以在将张量类型转换为(torch.cuda.TensorFloat)并在GPU上进行计算。
②.torch.autograd:用于构建计算图形并自动获取渐变的包。
③.torch.nn:具有共同层和成本函数的神经网络库。
④.torch.optim:具有通用优化算法(如SGD,Adam等)的优化包。

20.2Pytorch安装配置

本章的环境:python3.6,pytorch0.4.1,windows
windows下安装pytorch0.4.1的方法如下:


登录pytorch官网(https://pytorch.org/),选择安装配置内容,具体如下:

在命令行输入以下安装命令:

验证pytorch安装是否成功及安装版本号:

运行结果
0.4.1

20.3Pytorch实例

我觉得入门最快、最有效的方法就是实战,通过一些实际案例的学习,收获往往要好于从简单概念入手。本节内容安排大致如下:
先从我们熟知的numpy开始,看如何使用numpy实现正向传播和反向传播开始,接着介绍与pytorch中与Numpy相似的Tensor,如何实现同样功能,然后依次介绍如何使用autograd、nn、optim等等模块实现正向传播和反向传播。最后介绍两个完整实例:一个是回归分析、一个是卷积神经网络。

20.3.1利用Numpy实现正向和反向传播(简单实例)

利用numpy进行正向传播和反向传播,我们先介绍一个简单实例,然后由此推广到一般情况,最后用Pytorch实现自动反向传播。
简单实例主要信息如下:
数据量(N)为1,输入数据维度或节点数(D_in)为2,隐含层节点(H)为2,输出层维度或节点数(D_out)为2,输入到隐含层的权值矩阵为w1,隐含层到输出层的权值矩阵为w2,隐含层的激活函数为Relu(np.maximum(h, 0)),网络结构及矩阵、向量信息如下:
简单示例的神经网络结构图为:


1、正向传播
正向传播示意图(看蓝色细线部分):

具体实现步骤如下:
(1)导入需要的库或模块
这个简单实例,主要用到numpy及相关运算。

(2)生成输入数据、权重初始值等

运行结果
x的值:[[1 2]],
y的值:[[1 2]],
w1的初始值:[[0. 0.1]
[0.2 0.3]],
w2的初始值:[[0. 0.1]
[0.2 0.3]]
(3)前向传播并计算预测值

运行结果
h的值:[[0.4 0.7]],
h_relu的值:[[0.4 0.7]],
y_pred的初始值:[[0.14 0.25]]

其中dot是numpy实现内积运算,该运算规则示意图如下:

 

(4)计算损失值

3.8021

2、反向传播
反向传播示意图(看绿色粗线部分)


(1)具体求导步骤如下

运行结果
对y_pred求导:[[-1.72 -3.5 ]],
对W2求导:[[-0.688 -1.4 ]
[-1.204 -2.45 ]],
对h_relu求导:[[-0.35 -1.394]]
h的值:[[-0.35 -1.394]],
对h求导:[[-0.688 -1.4 ]
[-1.204 -2.45 ]],
对w1求导:[[-0.35 -1.394]
[-0.7 -2.788]]
(2)根据梯度更新权重参数

运行结果

更新w1:[[3.50000000e-07 1.00001394e-01]
[2.00000700e-01 3.00002788e-01]],
更新w2:[[6.88000000e-07 1.00001400e-01]
[2.00001204e-01 3.00002450e-01]]
其中涉及对向量或矩阵求导公式推导可参考:
https://blog.csdn.net/DawnRanger/article/details/78600506
至此利用numpy求正向和反向传播就结束了,接下来,我们看一般情况,即包含批量数据、一般维度、对权重进行多次迭代运算。

20.3.2利用Numpy实现正向和反向传播(一般情况)

上节我们用一个简单实例,说明如何利用numpy实现正向和反向传播,有了这个基础之后,我们接下来介绍利用numpy实现一般情况的正向和反向传播,具体代码如下:

20.3.3Pytorch的Tensor实现正向和反向传播

Numpy是一个很棒的框架,但它不能利用GPU来加速其数值计算。 对于现代深度神经网络,GPU通常提供50倍或更高的加速,所以不幸的是,numpy对于现代深度学习来说还不够。
在这里,我们介绍最基本的PyTorch概念:Tensor。 PyTorch Tensor在概念上与numpy数组相同:Tensor是一个n维数组,PyTorch提供了许多用于在这些Tensors上运算的函数。 Tensors可以跟踪计算图和梯度,也可用作科学计算的通用工具。
与numpy不同,PyTorch Tensors可以利用GPU加速其数值计算。 要在GPU上运行PyTorch Tensor,只需将其转换为新的数据类型即可。
在这里,我们使用PyTorch Tensors将双层网络与随机数据相匹配。 像上面的numpy示例一样,我们需要手动实现通过网络的正向和反向传播:

20.3.4利用Tensor和autograd实现自动反向传播

在上面的例子中,我们不得不手动实现神经网络的前向和后向传递。手动实现反向传递对于小型双层网络来说并不是什么大问题,但对于大型复杂网络来说,很快就会变得非常繁琐。
是否有更高效的方法呢?我们可以使用自动微分来自动计算神经网络中的反向传播。 PyTorch中的autograd包提供了这个功能。使用autograd时,网络的正向传递将定义计算图形;图中的节点将是张量,边将是从输入张量产生输出张量的函数。通过此图反向传播,您可以轻松计算梯度。
这听起来很复杂,在实践中使用起来非常简单。每个Tensor代表计算图中的节点。如果x是具有x.requires_grad = True的Tensor,则x.grad是另一个Tensor,相对于某个标量值保持x的梯度。
在这里,我们使用PyTorch Tensors和autograd来实现我们的双层网络;现在我们不再需要手动实现通过网络的反向传播了!

如果不用with torch.no_grad来更新权重参数,我们可以使用优化器来实现,具体可参考优化器(optim部分)。

20.3.5拓展autograd

我们可以用autograd实现自动反向求导,如果我们想要自己写函数,而又不用自动求导,该如何实现?
实现的方式就是自己定义函数,实现它的正向和反向求导。
在PyTorch中,我们可以通过定义torch.autograd.Function的子类并实现前向和后向函数来轻松定义我们自己的autograd运算符。 然后我们可以使用我们的新autograd运算符,通过构造一个实例并像函数一样调用它,传递包含输入数据的Tensors。
在下面例子中,我们定义了自己的自定义autograd函数来执行ReLU,并使用它来实现我们的双层网络:

【小知识】
Function与Module都可以对pytorch进行自定义拓展,使其满足网络的需求,但这两者还是有十分重要的不同:

(1)Function一般只定义一个操作,因为其无法保存参数,因此适用于激活函数、pooling等操作;Module是保存了参数,因此适合于定义一层,如线性层,卷积层,也适用于定义一个网络
(2)Function需要定义三个方法:__init__, forward, backward(需要自己写求导公式);Module:只需定义__init__和forward,而backward的计算由自动求导机制构成
(3)可以不严谨的认为,Module是由一系列Function组成,因此其在forward的过程中,Function和Tensor组成了计算图,在backward时,只需调用Function的backward就得到结果,因此Module不需要再定义backward。
(4)Module不仅包括了Function,还包括了对应的参数,以及其他函数与变量,这是Function所不具备的。

20.3.6对比TensorFlow

PyTorch autograd看起来很像TensorFlow:在两个框架中我们定义了一个计算图,并使用自动微分来计算梯度。两者之间最大的区别是TensorFlow的计算图是静态的,PyTorch使用动态计算图。
在TensorFlow中,我们定义计算图一次,然后一遍又一遍地执行相同的图,可能将不同的输入数据提供给图。在PyTorch中,每个前向传递定义了一个新的计算图。
静态图很好,因为你可以预先优化图形;例如,框架可能决定融合某些图形操作以提高效率,或者提出一种策略,用于在多个GPU或许多机器上分布图形。如果您反复使用相同的图表,那么可以分摊这个代价可能高昂的前期优化,因为相同的图表会反复重新运行。
静态和动态图表不同的一个方面是控制流程。对于某些模型,我们可能希望对每个数据点执行不同的计算;例如,可以针对每个数据点针对不同数量的时间步长展开循环网络;这种展开可以作为循环实现。使用静态图形,循环结构需要是图形的一部分;因此,TensorFlow提供了诸如tf.scan之类的运算符,用于将循环嵌入到图中。使用动态图形情况更简单:因为我们为每个示例动态构建图形,我们可以使用常规命令流程控制来执行每个输入不同的计算。
与上面的PyTorch autograd示例相比,这里我们使用TensorFlow来拟合一个简单的双层网:

20.3.7高级封装(nn模块)

计算图和autograd是一个非常强大的范例,用于定义复杂的运算符并自动获取导数;然而,对于大型神经网络,原始autograd封装级别较低,需要编写很多代码。
在构建神经网络时,我们经常考虑将计算安排到层中,其中一些层具有可学习的参数,这些参数将在学习期间进行优化。
在TensorFlow中,像Keras,TensorFlow-Slim和TFLearn这样的软件包提供了对构建神经网络有用的原始计算图形的更高级别的抽象。
在PyTorch中也有更高一级的封装,nn包服务于同样的目的。 nn包定义了一组模块,它们大致相当于神经网络层。模块接收输入张量并计算输出张量,但也可以保持内部状态,例如包含可学习参数的张量。 nn包还定义了一组在训练神经网络时常用的有用损失函数。
有关torch.nn的进一步介绍,大家可参考:
http://blog.leanote.com/post/1556905690@qq.com/torch.nn
在这个例子中,我们使用nn包来实现我们的双层网络:

20.3.8 优化器(optim)

到目前为止,我们通过手动改变持有可学习参数的Tensors来更新模型的权重(使用torch.no_grad()或.data以避免在autograd中跟踪历史记录)。 对于像随机梯度下降这样的简单优化算法来说,这不是一个巨大的负担,但在实践中,我们经常使用更复杂的优化器如AdaGrad,RMSProp,Adam等来训练神经网络。
PyTorch中的optim包抽象出优化算法的思想,并提供常用优化算法的实现。
在这个例子中,我们将使用nn包像以前一样定义我们的模型,但我们将使用optim包提供的Adam算法优化模型:

20.3.9 自定义网络层

有时,您需要指定比现有模块序列更复杂的模型; 对于这些情况,您可以通过继承父类nn.Module的方法定义自己的模块,并定义一个接收输入Tensors的forward,并使用其他模块或Tensors上的其他autograd操作生成输出Tensors。
在这个例子中,我们将我们的双层网络实现为自定义Module子类:

20.3.10 控制流与参数共享

作为动态图和权重共享的一个例子,我们实现了一个非常奇怪的模型:一个全连接的ReLU网络,中间会随机选择1到4层隐藏层,重复使用相同的权重多次 计算最里面的隐藏层。
对于这个模型,我们可以使用普通的Python流控制来实现循环,并且我们可以通过在定义正向传递时多次重复使用相同的模块来实现最内层之间的权重共享。
我们可以轻松地将此模型实现为Module子类:

20.3.11 小试牛刀:用Tensor实现线性回归

本节主要介绍如何利用autograd/Tensor实现线性回归,以此感受autograd的便捷之处。
(1)导入需要的库

(2)生成输入数据x及目标数据y
设置随机数种子,为了在不同人电脑上运行时下面的输出一致

(3)查看x,y数据分布情况

(4)初始化权重参数

(5)训练模型

运行结果:
tensor([[1.9769]], requires_grad=True) tensor([[3.3058]], requires_grad=True)

20.3.12 小试牛刀:用nn训练CIFAR10

本节使用nn来构建卷积神经网络,采用了nn.Module及nn. functional等模块,因为nn是pytorch的一个较高级的封装,所以整个代码非常简洁,无需考虑很多细节。
(1)CIFAR10数据集简介
CIFAR-10数据集由10类32x32的彩色图片组成,一共包含60000张图片,每一类包含6000图片。其中50000张图片作为训练集,10000张图片作为测试集。
CIFAR-10数据集被划分成了5个训练的batch和1个测试的batch,每个batch均包含10000张图片。测试集batch的图片是从每个类别中随机挑选的1000张图片组成的,训练集batch以随机的顺序包含剩下的50000张图片。不过一些训练集batch可能出现包含某一类图片比其他类的图片数量多的情况。训练集batch包含来自每一类的5000张图片,一共50000张训练图片。下图显示的是数据集的类,以及每一类中随机挑选的10张图片

下载地址:https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
(2)采用LeNet卷积神经网络
LeNet神经网络的结构如下图:

网络结构说明:
①input: 神经网络的输入是一张 32x32 的灰度图像
②conv1: 第一层是一个卷积层,卷积核(kernel size)大小 5x5 ,步长(stride)为 1 ,不进行padding,所以刚才的输入图像,经过这层后会输出6张 28x28 的特征图(feature map)。其中卷积后的大小28,根据(n+2p-f)/s+1得到,n(输入大小)=32,p(填补)=0,f(卷积核大小)=5,s(步幅长度)=1,由此可得:32-5+1=28。
③maxpooling2: 接下来是一个降采样层,用的是maxpooling,stride为 2 , kernel size为 2x2 ,subsampling之后,输出6张 14 x 14的feature map。
④conv3: 第三层又是一个卷积层,kernel size和stride均与第一层相同,不过最后要输出16张feature map。卷积后大小为10,该值根据(n+2p-f)/s+1得到,n(输入大小)=14,p(填补)=0,f(卷积核大小)=5,s(步幅长度)=1,由此可得:14-5+1=10。
⑤maxpooling4:第四层,又是一个maxpooling。
⑥fc5:第五层开始就是全连接(fully connected layer)层,把第四层的feature map摊平,然后做矩阵运算,输出是120个节点。
⑦fc6:输出是84个节点。
⑧output:最后一步是Gaussian Connections,采用了RBF函数(即径向欧式距离函数),计算输入向量和参数向量之间的欧式距离。目前一般采用Softmax。
(3)导入需要的模块

(4)加载数据
利用torchvision可以很方便的加载数据,同时对数据进行规范化处理。

运行结果
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data\cifar-10-python.tar.gz
Files already downloaded and verified
(5)可视化其中部分图形

运行结果:
cat car car horse

(6)构建网络
需引用学习参数的层放在构造函数__init__中,无需引用学习参数的层放在forward函数中。

(7)定义损失函数及优化器

(8)训练模型

运行结果
[1, 2000] loss: 2.192
[1, 4000] loss: 1.840
[1, 6000] loss: 1.672
[1, 8000] loss: 1.565
[1, 10000] loss: 1.511
[1, 12000] loss: 1.473
[2, 2000] loss: 1.387
[2, 4000] loss: 1.345
[2, 6000] loss: 1.330
[2, 8000] loss: 1.299
[2, 10000] loss: 1.304
[2, 12000] loss: 1.266
Finished Training
(9)测试模型
我们已经在训练数据集上循环2次。 我们先检查网络是否已经学到了什么。
我们将通过预测神经网络输出的类标签来检查这一点,并根据地面实况进行检查。 如果预测正确,我们将样本添加到正确预测列表中。
我们先从测试集中显示一个图像。

运行结果
GroundTruth: cat ship ship plane

(10)查看预测结果

运行结果
Predicted: cat ship ship ship
从这个结果来看,虽然只循环了2次,但4张图片,已识别3张。
接下来我们看在全数据的运行情况。
(11)看神经网络在整个数据集上的表现

运行结果
Accuracy of the network on the 10000 test images: 55 %

(12)查看各类别的性能

运行结果
Accuracy of plane : 52 %
Accuracy of car : 63 %
Accuracy of bird : 35 %
Accuracy of cat : 26 %
Accuracy of deer : 30 %
Accuracy of dog : 50 %
Accuracy of frog : 75 %
Accuracy of horse : 70 %
Accuracy of ship : 77 %
Accuracy of truck : 70 %

(13)GPU上运行
如果在GPU上运行以上网络,可作如下设计

更多pytorch实例可参考:
https://pytorch.org/tutorials/beginner/pytorch_with_examples.html

19.1 Jupyter Notebook概述

说话说得好,“磨刀不误砍柴工”,但有时选择比磨刀更重要。比如,砍树可以用斧头、砍刀、电锯等,如果选择电锯,不断不需要磨刀,而且效率也是其它工具的几倍。
Jupyter Notebook就是一把好用、高效、安全的"电锯"。目前Jupyter Notebook已成为Kaggle比赛中提交代码的主要方式。它广泛用于数据科学和机器学习领域,是运行深度学习实验的首先方法(keras之父、Kaggle竞赛教练 弗朗索∙肖莱所言)。
Jupyter Notebook(此前被称为 IPython notebook)是以网页的形式打开,可以在网页页面中直接编写代码和运行代码,代码的运行结果也会直接在代码块下显示的程序。如在编程过程中需要编写说明文档,可在同一个页面中直接编写,便于做及时的说明和解释。Jupyter Notebook有以下特点:
(1)编程时具有语法高亮、缩进、tab补全的功能。
(2)可直接通过浏览器运行代码,同时在代码块下方展示运行结果。
(3)以富媒体格式展示计算结果。丰富媒体格式,包括:HTML,LaTeX,PNG,SVG等。
(4)对代码编写说明文档或语句时,支持Markdown语法。
(5)支持使用LaTeX编写数学性说明。
(6)可以直接生成Python脚本、运行Python代码和脚本、或保存为PDF、XML等格式。
(7)可以对冗长的代码拆分为可独立运行的短代码,也可把一些短代码合并为一段长代码。
(8)交互式运行代码,如果后面代码出现问题,不必运行前面的代码。

19.2安装配置Python

(1)连接到Anaconda官网(https://www.anaconda.com/download/)下载对应环境、对应位数的软件包。建议下载Python3系列的,如下图:

(2)把下载软件(Linux对应是一个.sh脚本,大约640M左右,windows环境是一个.exe文件)存放在Linux服务器上,然后执行该脚本即可。
在Linux下安装下安装
①先下载管理包Anaconda2-4.0.0-Linux-x86_64.sh,
②在Linux命令行下运行:

③按缺省步骤即可,最后有一个提示,是否把当前路径存放到配置文件.bashrc中,选择 yes就可。
④安装成功后,如果还需要安装或更新其他库,只要在命令行执行:conda install 库名称或conda update 库名称,如安装tensorflow,只需要执行 conda install tensorflow即可。
当然也可用pip安装。
⑤可以用conda list 查看已安装的包或库
在windows下安装:
①从python官网下载Anaconda3-4.4.0-Windows-x86_64.exe或更高版本的,下载时注意选择64位还是32位(window8之后一般都是64位)
②双击该文件,开始执行,基本点next就可
③安装完成后,如果还需要安装或更新其他库,只要在cmd中执行:conda install 库名称或conda update 库名称。
如安装tensorflow,只需要执行 conda install tensorflow即可。当然也可用pip安装
④在cmd中可以用conda list 查看已安装的包或库


conda的常用命令:

19.3 Jupyter配置

下面是配置Jupyter Notebook的主要步骤。这是在Linux环境的配置方法,如果在window下,无需任何配置,启动Jupyter Notebook之后,自动弹出一个网页(网址为:localhost:8888),点击其中的new下拉菜单,选择pyhton3,就可进行编写代码、运行代码了。
1)生成配置文件

将在当前用户目录下生成文件:.jupyter/jupyter_notebook_config.py
2)生成当前用户登录jupyter密码
打开ipython, 创建一个密文密码

3)修改配置文件

进行如下修改:

4)启动jupyter notebook

然在浏览器上,输入IP:port,即可看到如下类似界面。

然后,点击New下列菜单,选择python3,将弹出Python编写界面,我们就可以在浏览器进行开发调试Python或keras、Tensorflow、Pytorch等程序。

19.4 Jupyter使用实例

以下以Linux环境为例,Windows环境基本相同。
(1)如何执行cell中的代码
同时按Shift键和Enter键即可。以下为代码示例。

运行结果
开发语言:python
(2)执行一些简单shell命令

运行结果:

(3)导入该脚本(或模块),并查看该模块的功能简介

(4)执行python脚本

【说明】
为了使该脚本有更好的移植性,可在第一行加上一句#!/usr/bin/python
运行.py文件时,python自动创建相应的.pyc文件,如下图,.pyc文件包含目标代码(编译后的代码),它是一种python专用的语言,以计算机能够高效运行的方式表示python源代码。这种代码无法阅读,故我们可以不管这个文件。
(5)添加注释或说明文档

(6)可以像word文档一样添加目录
为使你的jupyter更加方便快捷,我们可以对notebook内容分章节,然后对这些章节建立目录,我们要浏览或查看某个章节,只需点击对应目录,就可跳到对应位置,非常方便,如下图,需要安装一个扩展模块:jupyter_contrib_nbextensions,使用conda或pip安装,如下命令:conda install -c conda-forge jupyter_contrib_nbextensions
或pip install jupyter_contrib_nbextensions
具体安装配置,可参考以下博文:
https://www.jianshu.com/p/f314e9868cae
下图为效果图:

(7)修改文件名称
jupyter的文件自动保存,名称也是自动生成的,对自动生成的文件名称,我们也可重命名,具体步骤如下;
点击目前文件名称:

然后重命名,并点击rename即可,具体可参考下图:

(8)画图也很方便
以下是画一条抛物线的代码,在jupyter显示图形,需要加上一句:%matplotlib inline,具体代码如下:

(9)在jupyter 里查看函数、模块的帮助信息也很方便。
查看命令函数及帮助等信息
命令. 然后按tab键 可查看所有的函数
命令? 可查看对应命令的帮助信息

查看a1数组,可以使用的函数

查看argmax函数的具体使用方法,只要在函数后加上一个问号(?),然后运行,就会弹出一个详细帮助信息的界面,具体可参考下图:

(10)编写公式也很方便

然后执行该cell,就可看到如下结果:

更多LaTeX的使用方法,可参考:
https://www.jianshu.com/p/93ccc63e5a1b

18.1集成学习概述

集成学习的原理正如盲人摸象这个古代寓言所揭示的道理类似:一群盲人第一次遇到大象,想要通过触觉来了解大象。每个人都摸到大象身体的不同部位。但只摸到不同部分,比如鼻子或一条腿。这些人描述的大象是这样的:“它像一条蛇”,“像一根柱子或一棵树”,等等。这些盲人就好比机器学习模型,每个人都是根据自己的假设,并从自己的角度来理解训练数据的多面性。每个人都得到真相的一部分,但不是全部真相。将他们的观点汇集在一起,你就可以得到对数据更加准确的描述。大象是多个部分的组合,每个盲人说的都不完全准确,但综合起来就成了一个相当准确的观点。
集成学习(ensemble learning)可以说是现在非常火爆的机器学习方法了。目前,集成方法在许多著名的机器学习比赛(如 Netflix、KDD 2009 和 Kaggle 比赛)中能够取得很好的名次。
集成学习本身不是一个单独的机器学习算法,而是通过构建并结合多个机器学习器来完成学习任务。也就是我们常说的“博采众长”。集成学习可以用于分类问题集成,回归问题集成,特征选取集成,异常点检测集成等等,可以说所有的机器学习领域都可以看到集成学习的身影。
集成学习的主要思想:对于一个比较复杂的任务,综合许多人的意见来进行决策往往比一家独大好,正所谓集思广益。其过程如下:

 

一般来说集成学习可以分为三大类:
①用于减少方差的bagging(方差描述的是预测值作为随机变量的离散程度)
②用于减少偏差的boosting(偏差描述的是预测值和真实值之间的差异,即提高拟合能力)
③用于提升预测效果的stacking

18.1.1 Bagging

Bagging是引导聚合的意思。减少一个估计方差的一种方式就是对多个估计进行平均。
Bagging使用装袋采样来获取数据子集训练基础学习器。通常分类任务使用投票的方式集成,而回归任务通过平均的方式集成。
给定一个大小为n的训练集 D,Bagging算法从中均匀、有放回地选出 m个大小为 n' 的子集Di,作为新的训练集。在这 m个训练集上使用分类、回归等算法,则可得到 m个模型,再通过取平均值、取多数票等方法综合产生预测结果,即可得到Bagging的结果。具体如下图:

对于Bagging需要注意的是,每次训练集可以取全部的特征进行训练,也可以随机选取部分特征训练,例如随机森林就是每次随机选取部分特征。
常用的集成算法模型是随机森林和随机树等。
在随机森林中,每个树模型都是装袋采样训练的。另外,特征也是随机选择的,最后对于训练好的树也是随机选择的。
这种处理的结果是随机森林的偏差增加的很少,而由于弱相关树模型的平均,方差也得以降低,最终得到一个方差小,偏差也小的模型。

18.1.2 boosting

Boosting指的是通过算法集合将弱学习器转换为强学习器。boosting的主要原则是训练一系列的弱学习器,所谓弱学习器是指仅比随机猜测好一点点的模型,例如较小的决策树,训练的方式是利用加权的数据。在训练的早期对于错分数据给予较大的权重。
对于训练好的弱分类器,如果是分类任务按照权重进行投票,而对于回归任务进行加权,然后再进行预测。boosting和bagging的区别在于是对加权后的数据利用弱分类器依次进行训练。
boosting是一族可将弱学习器提升为强学习器的算法,这族算法的工作机制类似:
(1)先从初始训练集训练出一个基学习器;
(2)再根据基学习器的表现对训练样本分布进行调整,使得先前基学习器做错的训练样本在后续受到更多关注;
(3)基于调整后的样本分布来训练下一个基学习器;
(4)重复进行上述步骤,直至基学习器数目达到事先指定的值T,最终将这T个基学习器进行加权结合。具体步骤如下图

如果上面这个图还不太直观,大家可参考以下简单示例:
1、假设我们有如下样本图:

图1
2、第一次分类

图2
第2次分类

图3
在图2中被正确测的点有较小的权重(尺寸较小),而被预测错误的点(+)则有较大的权重(尺寸较大)
第3次分类

图4
在图3中被正确测的点有较小的权重(尺寸较小),而被预测错误的点(-)则有较大的权重(尺寸较大)。

第4次综合以上分类

下面描述的算法是最常用的一种boosting算法,叫做AdaBoost,表示自适应boosting。

AdaBoost算法每一轮都要判断当前基学习器是否满足条件,一旦条件不满足,则当前学习器被抛弃,且学习过程停止。
AdaBoost算法中的个体学习器存在着强依赖关系,应用的是串行生成的序列化方法。每一个基生成器的目标,都是为了最小化损失函数。所以,可以说AdaBoost算法注重减小偏差。
由于属于boosting算法族,采用的是加性模型,对每个基学习器的输出结果加权处理,只会得到一个输出预测结果。所以标准的AdaBoost只适用于二分类任务。基于Boosting思想的除AdaBoost外,还有GBDT、XGBoost等。

18.1.3 Stacking

将训练好的所有基模型对训练基进行预测,第j个基模型对第i个训练样本的预测值(概率值或标签)将作为新的训练集中第i个样本的第j个特征值,最后基于新的训练集进行训练。同理,预测的过程也要先经过所有基模型的预测形成新的测试集,最后再对测试集进行预测。如下图所示。

上图可简化为:

其中Meta-Classifier在实际应用中通常使用单层logistic回归模型。
具体算法为:

18.1.3 .1Stacking中元分类层

为何要Meta-Classifier层?设置该层的目的是啥?其原理是什么?等等或许你还不很清楚,没关系。你看了下面这个说明或许就清楚多了。
让我们假设有三个学生名为LR,SVM,KNN,他们争论一个物理问题,他们对正确的答案可能有不同的看法:

他们认为没有办法相互说服他们的情况,他们通过平均估计他们做民主的事情,这个案例是14.他们使用了最简单的集合形式-AKA模型平均。

他们的老师,DL小姐 - 一位数学老师 - 见证了学生们所拥有的论点并决定提供帮助。她问“问题是什么?”,但是学生们拒绝告诉她(因为他们知道提供所有信息对他们不利,除了他们认为她可能会觉得愚蠢他们在争论这么微不足道的事情)。然而,他们确实告诉她这是一个与物理相关的论点。

在这种情况下,教师无法访问初始数据,因为她不知道问题是什么。然而,她确实非常了解学生 - 他们的优点和缺点,她决定她仍然可以帮助解决这个问题。使用历史信息,了解学生过去的表现,以及她知道SVM喜欢物理并且在这个课程中表现优异的事实(加上她的父亲在青年科学家的物理学院工作),她认为最多适当的答案会更像17。

在这种情况下,教师(DL)是元学习者。她使用其他模型(学生)输出的结果作为输入数据。然后,她将其与历史信息结合起来,了解学生过去的表现,以便更好地估计(并帮助解决冲突)。

然而......物理老师RF先生的意见略有不同。他一直在那里,但他一直等到这一刻才行动! RF先生最近一直在教授LR私人物理课程,以提高他的成绩(错过DL不知道的事情),他认为LR对最终估计的贡献应该更大。因此他声称正确的答案更像是16!

在这种情况下,RF先生也是一个元学习者,他用不同的逻辑处理历史数据 - 他可以访问比DL小姐更多的来源(或不同的历史信息)。

只有校长GBM做出决定,才能解决此争议! GBM不知道孩子们说了什么,但他很了解他的老师,他更热衷于信任他的物理老师(RF)。他总结答案更像是16.2。

在这种情况下,校长是元级学习者或元学习者的元学习者,并且通过处理他的老师的历史信息,他仍然可以提供比他们的结果的简单平均值更好的估计。
参考文档:
http://blog.kaggle.com/2017/06/15/stacking-made-easy-an-introduction-to-stacknet-by-competitions-grandmaster-marios-michailidis-kazanova/

18.1.3.2Stacking的几种方法

1) 使用分类器产生的特征输出作为meta-classifier的输入
基本使用方法就是,使用前面分类器产生的特征输出作为最后总的meta-classifier的输入数据,以下为利用stacking的基本使用方法实例。
(1)生成数据

(2)导入需要的库

(3)训练各种基模型

运行结果
3-fold cross validation:

Accuracy: 0.91 (+/- 0.01) [KNN]
Accuracy: 0.91 (+/- 0.06) [Random Forest]
Accuracy: 0.92 (+/- 0.03) [Naive Bayes]
Accuracy: 0.95 (+/- 0.03) [StackingClassifier]
(4)可视化结果

运行结果

使用网格方法选择超参数

运行结果
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.927 +/- 0.02 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
0.913 +/- 0.03 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.933 +/- 0.02 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
0.940 +/- 0.02 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
Best parameters: {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
Accuracy: 0.94

2)使用类别概率值作为meta-classfier的输入
另一种使用第一层基本分类器产生的类别概率值作为meta-classfier的输入,这种情况下需要将StackingClassifier的参数设置为 use_probas=True。如果将参数设置为 average_probas=True,那么这些基分类器对每一个类别产生的概率值会被平均,否则会拼接。
例如有两个基分类器产生的概率输出为:
classifier 1: [0.2, 0.5, 0.3]
classifier 2: [0.3, 0.4, 0.4]
1) average = True :
产生的meta-feature 为:[0.25, 0.45, 0.35]
2) average = False:
产生的meta-feature为:[0.2, 0.5, 0.3, 0.3, 0.4, 0.4]
以下为具体实例

运行结果
3-fold cross validation:

Accuracy: 0.91 (+/- 0.01) [KNN]
Accuracy: 0.91 (+/- 0.06) [Random Forest]
Accuracy: 0.92 (+/- 0.03) [Naive Bayes]
Accuracy: 0.94 (+/- 0.03) [StackingClassifier]

显然,用stacking方法的精度(Accuracy: 0.94)明显好于单个模型的精度。
3)使用堆叠分类及网格搜索
使用堆叠分类及网格搜索(Stacked Classification and GridSearch)方法,要为scikit-learn网格搜索设置参数网格,我们只需在参数网格中提供估算器的名称 - 在meta-regressor的特殊情况下,我们附加'meta-'前缀即可,以下为代码实例。

运行结果
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.967 +/- 0.01 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
0.967 +/- 0.01 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.967 +/- 0.01 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
0.967 +/- 0.01 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
Best parameters: {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
Accuracy: 0.97
最一句是各参数最佳匹配模型的结果,显然,这个精度高于其他情况的精度。
如果我们计划多次使用回归算法,我们需要做的就是在参数网格中添加一个额外的数字后缀,如下所示:

运行结果
0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}
0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.967 +/- 0.01 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-....
0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.967 +/- 0.01 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
.......................................
0.967 +/- 0.01 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
Best parameters: {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
Accuracy: 0.97

StackingClassifier还可以对分类器参数进行网格搜索。 但是,由于目前scikit-learn中GridSearchCV的实现,不可能同时搜索不同分类器和分类器参数。 例如,虽然以下参数字典有效。
4)给不同及分类器不同特征
给不同及分类器不同特征是对训练基中的特征维度进行操作的,这次不是给每一个基分类器全部的特征,而是给不同的基分类器分不同的特征,即比如基分类器1训练前半部分特征,基分类器2训练后半部分特征(可以通过sklearn 的pipelines 实现)。最终通过StackingClassifier组合起来。以下为代码实例。

参考文档:
https://rasbt.github.io/mlxtend/user_guide/classifier/StackingClassifier/

18.2投票分类器(VotingClassifier)

投票分类器的原理是结合了多个不同的机器学习分类器,使用多数票或者平均预测概率(软票),预测类标签。这类分类器对一组相同表现的模型十分有用,同时可以平衡各自的弱点。投票分类又可进一步分为多数投票分类(Majority Class Labels)、加权平均概率(soft vote,软投票)。

18.2.1多数投票分类(MajorityVote Class)

多数投票分类的分类原则为预测标签不同时,按最多种类为最终分类;如果预测标签相同时,则按顺序,选择排在第1的标签为最终分类。举例如下:
预测类型的标签为该组学习器中相同最多的种类:例如给出的分类如下
分类器1 -> 标签1
分类器2 -> 标签1
分类器3 -> 标签2
投票分类器(voting=‘hard’)则该预测结果为‘标签1’。
在各个都只有一个的情况下,则按照顺序来,如下:
分类器1 -> 标签2
分类器2 -> 标签1
最终分类结果为“标签2”

18.2.1.1Iris数据集概述

首先,我们取得数据,下面这个链接中有数据的详细介绍,并可以下载数据集。https://archive.ics.uci.edu/ml/datasets/Iris
从数据的说明上,我们可以看到Iris有4个特征,3个类别。但是,我们为了数据的可视化,我们只保留2个特征(sepal length和petal length)。数据可视化代码如下:

示例代码如下:

运行结果如下:
Accuracy: 0.90 (+/- 0.05) [Logistic Regression]
Accuracy: 0.93 (+/- 0.05) [Random Forest]
Accuracy: 0.91 (+/- 0.04) [naive Bayes]
Accuracy: 0.95 (+/- 0.05) [Ensemble]

18.2.2多数投票分类(MajorityVote Class)

相对于多数投票(hard voting),软投票返回预测概率值的总和最大的标签。可通过参数weights指定每个分类器的权重;若权重提供了,在计算时则会按照权重计算,然后取平均;标签则为概率最高的标签。
举例说明,假设有3个分类器,3个类,每个分类器的权重为:w1=1,w2=1,w3=1。如下表:

下面例子为线性SVM,决策树,K邻近分类器:

18.3自适应分类器(Adaboost)

Adaboost是一种迭代算法,其核心思想是针对同一个训练集训练不同的分类器(弱分类器),然后把这些弱分类器集合起来,构成一个更强的最终分类器(强分类器)。其算法本身是通过改变数据分布来实现的,它根据每次训练集之中每个样本的分类是否正确,以及上次的总体分类的准确率,来确定每个样本的权值。将修改过权值的新数据集送给下层分类器进行训练,最后将每次训练得到的分类器最后融合起来,作为最后的决策分类器。使用adaboost分类器可以排除一些不必要的训练数据特征,并放在关键的训练数据上面。
下面的例子展示了AdaBoost算法拟合100个弱学习器

输出结果为:
0.95996732026143794

18.4 Xgboost简介

18.4.1简介

Xgboost是很多CART回归树集成,CART树以基尼系数为划分依据。回归树的样本输出是数值的形式,比如给某人发放房屋贷款的数额就是具体的数值,可以是0到120万元之间的任意值。那么,这时候你就没法用上述的信息增益、信息增益率、基尼系数来判定树的节点分裂了,你就会采用新的方式,预测误差,常用的有均方误差、对数误差等。而且节点不再是类别,是数值(预测值),那么怎么确定呢,有的是节点内样本均值,有的是最优化算出来的比如Xgboost。
xgboot特点:
1、w是最优化求出来的
2、使用正则化防止过拟合的技术
3、支持分布式、并行化,树之间没有很强的上下依赖关系
4、支持GPU
下图就是一个CART的例子,CART会把输入根据属性分配到各个也子节点上,而每个叶子节点上面会对应一个分数值。下面的例子是预测一个人是否喜欢电脑游戏。将叶子节点表示为分数之后,可以做很多事情,比如概率预测,排序等等。

一个CART往往过于简单,而无法有效的进行预测,因此更加高效的是使用多个CART进行融合,使用集成的方法提升预测效率:

假设有两颗回归树,则两棵树融合后的预测结果如上图。
xgboost涉及 参数较多,具体使用可参考:https://cloud.tencent.com/developer/article/1111048
https://blog.csdn.net/han_xiaoyang/article/details/52665396

18.4.2 xgboost 实例

主要目的:利用多种预测方法,对房价进行预测
数据结构:
数据探索与预处理:
创建及优化模型:

18.4.2.1数据探索及数据预处理

(1)导入需要的库,并导入数据,查看前五行样本数据,参考文档:

The train data size before dropping Id feature is : (1460, 81)
The test data size before dropping Id feature is : (1459, 80)

The train data size after dropping Id feature is : (1460, 80)
The test data size after dropping Id feature is : (1459, 79)

(2)探索孤立点

(3)删除一些孤立点
删除房屋销售价(SalePr ice)小于300000(美元)并且居住面积(GrLivArea)平方英尺大于40003的记录。主要是上图中右下边这几个点。

(4)探索房价分布情况
分析目标变量(房价),画出房价分布图及QQ图,QQ图就是分位数图示法(Quantile Quantile Plot,Q-Q图主要用于检验数据分布的相似性,如果要利用Q-Q图来对数据进行正态分布的检验,则可以令x轴为正态分布的分位数,y轴为样本分位数,如果这两者构成的点分布在一条直线上,就证明样本数据与正态分布存在线性相关性,即服从正态分布。

由上图可知,目标变量是右倾斜的。 由于(线性)模型喜欢正态分布的数据,我们需要对房价特征进行转换,使其接近正态分布。

(5)对房价特征进行log转换

现在纠正了偏差,数据看起来更正常分布。
(6)连接训练数据和测试数据
为便于统一处理,我们需要把训练数据、测试数据集成在一起。对房价数据不做处理。

运行结果:
all_data size is : (2917, 79)
(7)查看缺失数据情况
以下我们查看各特征的缺失率

运行结果
Missing Ratio
PoolQC 99.691
MiscFeature 96.4
Alley 93.212
Fence 80.425
FireplaceQu 48.68
LotFrontage 16.661
GarageQual 5.451
GarageCond 5.451
GarageFinish 5.451
GarageYrBlt 5.451
GarageType 5.382
BsmtExposure 2.811
BsmtCond 2.811
BsmtQual 2.777
BsmtFinType2 2.743
BsmtFinType1 2.708
MasVnrType 0.823
MasVnrArea 0.788
MSZoning 0.137
BsmtFullBath 0.069
可视化这些数据

(8)查看数据的相关性

颜色越深,表示相关性越强。
(9)填充缺失值
以下我们对存在缺失值的特征分别进行处理,
PoolQC:数据描述表示NA表示“无池”。 这是有道理的,因为缺失值的比例很大(+ 99%),而且大多数房屋一般都没有游泳池。这里我们把缺失值改为None。

MiscFeature:数据描述表示NA表示“没有杂项功能,这里把缺失值改为None
all_data["MiscFeature"] = all_data["MiscFeature"].fillna("None")
类似把特征Alley 、Fence、FireplaceQu、GarageType, GarageFinish, GarageQual and GarageCond,进行相同处理,这些特征的缺失率都比较高。

LotFrontage(与街道连接的线性脚):由于连接到房产的每条街道的区域很可能与其附近的其他房屋有相似的区域,我们可以通过邻域的中位数LotFrontage填写缺失值。

GarageYrBlt,GarageArea和GarageCars:用0代替缺失数据(因为没有车库=这样的车库没有车辆。)

对BsmtFinSF1, BsmtFinSF2, BsmtUnfSF, TotalBsmtSF, BsmtFullBath and BsmtHalfBath, MasVnrArea做同样处理。

对BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2','MasVnrType特征的缺失值改为None。

MSZoning(一般分区分类):'RL'是迄今为止最常见的值。 所以我们可以用'RL'来填补缺失值,取频度最大的那个数据,采用mode()[0]的格式。

对特征Electrical,KitchenQual,Exterior1st,Exterior2nd,SaleType做相同处理。

Utilities:对于此分类功能,所有记录都是“AllPub”,除了一个“NoSeWa”和2个NA。 由于带有“NoSewa”的房子位于训练集中,因此该功能无助于预测建模。 然后我们可以安全地删除它。

通过以上缺失值的填充,我们再看一下缺失情况

运行结果为
没有缺失值特征了!
(10)对一些分类的数值特征转换。
这里使用LabelEncoder 对不连续的数字或者文本进行编号。

【说明】
apply(str)与astype(str)功能相同,一般前者快于后者。

(11)对类别特征进行编码
这里使用LabelEncoder 对不连续的数字或者文本进行编号。

(12)增加特征
由于区域相关特征对于确定房价非常重要,这里将增加了一个特征,即每个房屋的地下室总面积,一楼和二楼面积之和

(13)查看一些特征的歪斜程度
这里主要对一些数值型特征进行分析

运行结果

(14)对这些倾斜特征进行box-cox变换
Box-Cox变换是统计建模中常用的一种数据变换,用于连续的响应变量不满足正态分布的情况。Box-Cox变换使线性回归模型满足线性性、独立性、方差齐性以及正态性的同时,又不丢失信息。
使用Box-Cox变换族一般都可以保证将数据进行成功的正态变换,但在二分变量或较少水平的等级变量的情况下,不能成功进行转换,此时,我们可以考虑使用广义线性模型,如LOGUSTICS模型、Johnson转换等.
Box-Cox变换后,残差可以更好的满足正态性、独立性等假设前提,降低了伪回归的概率

其中确定λ是关键,如何确定λ?一般采用最大似然估计来求。该值一般在[-5,5]之间。
一般y分布为左偏(左边比较陡峭),则λ=0;如果y为右偏,取λ>0。

(15)得到虚拟分类特征,对标称类别转换为one-hot编码。

运行结果:
(2917, 220)
(16)得到新的训练集、测试集

查看他们的维度

运行结果
(1458, 220)
(1459, 220)
至此,数据探索及预处理就基本完成,接下来开始创建模型。

18.4.2.2创建模型

(1)导入需要的库

(2)使用K折交叉验证,其中k=5
K折交叉验证简介:
将数据集平均分割成K个等份
使用1份数据作为测试数据,其余作为训练数据
计算测试准确率
使用不同的测试集,重复2、3步骤
对测试准确率做平均,作为对未知数据预测准确率的估计
如下图

【说明】
# 这里的cross_val_score将交叉验证的整个过程连接起来,不用再进行手动的分割数据
# cv参数用于规定将原始数据分成多少份
# scoring:该参数来控制它们对 estimators evaluated (评估的估计量)应用的指标。
对分类模型,该值可以为‘accuracy’ 或‘f1’;对回归模型,可以为‘explained_variance’, ‘neg_mean_squared_error’等。对性能来说,越小越好,最佳为0.
#多种评估指标:回归模型
均方差(MSE):

均方误差对数(MSLE):

平均绝对误差(MAE):

更多模型评估指标,可参考:http://sklearn.apachecn.org/cn/0.19.0/modules/model_evaluation.html
(3)使用模型
在模型选择上,“没有免费的午餐”。为了比较模型间的性能,这里使用多种模型。首先使用3 种回归模型(Linear Regression,Lasso,Ridge。
使用LASSO Regression :
该模型可能对异常值非常敏感。 所以我们需要让它们更加健壮。 为此,我们在管道上使用sklearn的Robustscaler()方法。

使用Elastic Net Regression :
增益对异常值有所增强,故这里也使用Robustscaler()方法

【ElasticNet简介】
ElasticNet 是一种使用L1和L2先验作为正则化矩阵的线性回归模型.这种组合用于只有很少的权重非零的稀疏模型,比如:class:Lasso, 但是又能保持:class:Ridge 的正则化属性.我们可以使用ρ(ρ的具体位置,请看下面这个表达式)参数来调节L1和L2的凸组合(一类特殊的线性组合)。
当多个特征和另一个特征相关的时候弹性网络非常有用。Lasso 倾向于随机选择其中一个,而弹性网络更倾向于选择两个.
在实践中,Lasso 和 Ridge 之间权衡的一个优势是它允许在循环过程(Under rotate)中继承 Ridge 的稳定性.
弹性网络的目标函数是最小化:

ElasticNetCV 可以通过交叉验证来用来设置参数:
alpha (α),l1_ratio (ρ)

使用Kernel Ridge Regression :

使用Gradient Boosting Regression :
其对异常值具有鲁棒性

使用XGBoost :

使用LightGBM

18.4.2.3看各模型的性能

让我们通过评估交叉验证rmsle错误来了解这些基本模型如何对数据执行

运行结果
Lasso score: 0.0032 (0.0002)
ElasticNet score: 0.0031 (0.0002)
Kernel Ridge score: 0.0034 (0.0004)
Gradient Boosting score: 0.0026 (0.0002)
Xgboost score: 0.0086 (0.0003)
LGBM score: 0.0026 (0.0002)

18.4.2.4堆叠(或集成)各模型

我们从这种平均基本模型的简单方法开始。 我们构建了一个新类来扩展scikit-learn与我们的模型,并且还包括封装和代码重用(继承)
(1)对各模型求平均值

(2)看集成后模型性能
这里集成ENet, GBoost, KRR, lasso这4种基本模型

运行结果
Averaged base models score: 0.0027 (0.0002)
不错!从初步结果来看,上面我们采用的最简单的堆叠方法确实提高了分数。 这将鼓励我们进一步探索不那么简单的堆叠方法。

18.4.2.5添加元模型

这种集成方法的基本思想如下:
在这种方法中,我们在平均基础模型上添加元模型,并使用这些基础模型的折叠后预测来训练我们的元模型。
培训部分的程序可以描述如下:
1)将整个训练集分成两个不相交的集(这里是train和holdout)
2)在第一部分(即train)训练几个基础模型
3)在第二部分(即holdout)测试这些基础模型
4)使用来自3)的预测(称为折叠外预测)作为输入,并使用正确的响应(目标变量)作为输出来训练更高级别的元模型。
前三个步骤是迭代完成的。 如果我们采用5-折(5-fold)堆叠,我们首先将训练数据分成5折(fold)。 然后我们将进行5次迭代。 在每次迭代中,我们训练每个基础模型4份并在剩余的一份数据上进行预测。
因此,经过5次迭代后,我们将确保使用整个数据进行折叠后预测,然后我们将使用这些预测作为新特征来训练第4步中的元模型。
对于预测部分,我们对测试数据上所有基础模型的预测进行平均,并将它们用作元特征,最终预测是使用元模型完成的。

以上步骤如下图所示:

数据生成过程的动态效果图:

有关stacking的介绍可参考:
http://blog.kaggle.com/2017/06/15/stacking-made-easy-an-introduction-to-stacknet-by-competitions-grandmaster-marios-michailidis-kazanova/
此外,下图从另一个角度来说明stacking的原理。
改为如下图(主要把上部分的列标题都为Model1)

如何理解这个图呢?我们通过一个简单实例来说明:
Train Data有890行。(请对应图中的上层部分)
每1次的fold,都会生成 712行 小train, 178行 小test。我们用Model 1来训练 712行的小train,然后预测 178行 小test。预测的结果是长度为 178 的预测值。
这样的动作走5次! 长度为178 的预测值 X 5 = 890 预测值,刚好和Train data长度吻合。这个890预测值是Model 1产生的,我们先存着,因为,一会让它将是第二层模型的训练来源。
重点:这一步产生的预测值我们可以转成 890 X 1 (890 行,1列),记作 P1 (大写P)
接着说 Test Data 有 418 行。(请对应图中的下层部分,对对对,绿绿的那些框框)
每1次的fold,712行 小train训练出来的Model 1要去预测我们全部的Test Data(全部!因为Test Data没有加入5-fold,所以每次都是全部!)。此时,Model 1的预测结果是长度为418的预测值。
这样的动作走5次!我们可以得到一个 5 X 418 的预测值矩阵。然后我们根据行来就平均值,最后得到一个 1 X 418 的平均预测值。
重点:这一步产生的预测值我们可以转成 418 X 1 (418行,1列),记作 p1 (小写p)
走到这里,你的第一层的Model 1完成了它的使命。
第一层还会有其他Model的,比如Model 2,同样的走一遍, 我们有可以得到 890 X 1 (P2) 和 418 X 1 (p2) 列预测值。
这样吧,假设你第一层有3个模型,这样你就会得到:
来自5-fold的预测值矩阵 890 X 3,(P1,P2, P3) 和 来自Test Data预测值矩阵 418 X 3, (p1, p2, p3)。
到第二层了
来自5-fold的预测值矩阵 890 X 3 作为你的Train Data,训练第二层的模型
来自Test Data预测值矩阵 418 X 3 就是你的Test Data,用训练好的模型来预测他们吧。

(1)堆叠平均模型类

(2)堆叠平均模型得分
为了使两种方法具有可比性(通过使用相同数量的模型),我们只是平均Enet,KRR和Gboost,然后我们添加lasso作为元模型。

运行结果
Stacking Averaged models score: 0.0026 (0.0002)
由此可知,通过添加元学习器,我们再次获得更好的分数。
这个结果比简单求平均值的得分Averaged base models score: 0.0027 (0.0002)
更好!

(3)集成StackedRegressor,XGBoost和LightGBM
我们将XGBoost和LightGBM添加到之前定义的StackedRegressor中。
我们首先定义一个rmsle评估函数

(4)最终培训和预测

运行结果
0.00165651517208
(5)计算xgboost

运行结果
0.00860202560688
(6)计算LightGBM:

运行结果
0.0016023894136

运行结果
RMSLE score on train data:
0.002334422609
由此看出,这似乎最简单的堆叠(或集成)方法确实提高了分数。
大家可参考:https://www.kaggle.com/serigne/stacked-regressions-top-4-on-leaderboard
集成学习非常成功,该算法不仅在挑战性的数据集上频频打破性能方面的记录,而且是 Kaggle 数据科学竞赛的获奖者常用的方法之一。
有关集成学习方法可参考:
https://zhuanlan.zhihu.com/p/25836678

17 降维简介

当特征选择完成后,可以直接训练模型了,但是可能由于特征矩阵过大,导致计算量大,训练时间长的问题,因此降低特征矩阵维度也是必不可少的。常见的降维方法除了以上提到的基于L1惩罚项的模型以外,另外还有主成分分析法(PCA)和线性判别分析(LDA),线性判别分析本身也是一个分类模型。PCA和LDA有很多的相似点,其本质是要将原始的样本映射到维度更低的样本空间中,但是PCA和LDA的映射目标不一样:PCA是为了让映射后的样本具有最大的发散性;而LDA是为了让映射后的样本有最好的分类性能。所以说PCA是一种无监督的降维方法,而LDA是一种有监督的降维方法。
PCA、LDA降维一般假设数据集为线性可分,如果用这两种方法,对线性不可分的数据集进行降维,效果往往不理想。本质上PCA和LDA还是一种线性变换。而线性不可分数据应该是很普遍的,对线性不可分数据集该如何进行降维呢?这里我们介绍一种核PCA方法,这样降维方法综合了核技巧及PCA思想,对非线性数据集降维有非常好的效果。
此外,这里我们还介绍SVD方法,这也是一种非常有效的降维方法。

17.1 PCA简介

主成分分析(Principal Components Analysis),简称PCA,是一种数据降维技术,用于数据预处理。一般我们获取的原始数据维度都很高,比如1000个特征,在这1000个特征中可能包含了很多无用的信息或者噪声,真正有用的特征才50个或更少,那么我们可以运用PCA算法将1000个特征降到50个特征。这样不仅可以去除无用的噪声,还能减少很大的计算量。
PCA算法是如何实现的?
简单来说,就是将数据从原特征空间转换到新的特征空间中,例如原始的空间是三维的(x,y,z),x、y、z分别是原始空间的三个基,我们可以通过某种方法,用新的坐标系(a,b,c)来表示原始的数据,那么a、b、c就是新的基,它们组成新的特征空间。在新的特征空间中,可能所有的数据在c上的投影都接近于0,即可以忽略,那么我们就可以直接用(a,b)来表示数据,这样数据就从三维的(x,y,z)降到了二维的(a,b)。
问题是如何求新的基(a,b,c)?
一般步骤是这样的:
1)对原始数据集做标准化处理。
2)求协方差矩阵。
3)计算协方差矩阵的特征值和特征向量。
4)选择前k个最大的特征向量,k小于原数据集维度。
5)通过前k个特征向量组成了新的特征空间,设为W。
6)通过矩阵W,把原数据转换到新的k维特征子空间。

17.2 PCA算法实现

这里以葡萄酒数据为例,数据集特征如下:

数据来源于:https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data
1)对原数据集做标准化处理
导入需要的库及数据

部分内容:

为便于后续处理,把数据集分为训练集和测试集,划分比例为7:3

对原数据进行标准化处理

2) 求协方差矩阵
这里使用numpy.cov函数,求标准化后数据的协方差矩阵

3)计算协方差矩阵的特征值和特征向量
使用np.linalg.eig函数,求协方差的特征值和特征向量

得到13个特征向量:
Eigenvalues
[ 4.8923083 2.46635032 1.42809973 1.01233462 0.84906459 0.60181514 0.52251546 0.08414846 0.33051429 0.29595018 0.16831254 0.21432212 0.2399553 ]
要实现降维,我们可以选择前k个最多信息(或方差最大)特征向量组成新的子集,由于特征值的大小决定了特征向量的重要性,因此,可以通过对特征值的排序,获取前k个特征值。特征值λ_i的方差贡献率是指特征值λ_i与所有特征值和的比例:

我们可以通过numpy.cumsum函数计算累计方差。

从这个图可以看出第一个主成分占了方差总和的40%左右,前两个主成分占了近60%。
4)选择前k个最大的特征向量,k小于原数据集维度
首先,按特征值按降序排序

5)通过前k个特征向量组成了新的特征空间,设为W。
为便于数据可视化,这里我们取k=2,实际上前2个特征值已占了总方差的近60%。

这样我们就可得到一个由这两个特征向量构成的13*2矩阵W:
Matrix W:
[[ 0.14669811 0.50417079]
[-0.24224554 0.24216889]
[-0.02993442 0.28698484]
[-0.25519002 -0.06468718]
[ 0.12079772 0.22995385]
[ 0.38934455 0.09363991]
[ 0.42326486 0.01088622]
[-0.30634956 0.01870216]
[ 0.30572219 0.03040352]
[-0.09869191 0.54527081]
[ 0.30032535 -0.27924322]
[ 0.36821154 -0.174365 ]
[ 0.29259713 0.36315461]]

6)通过矩阵W,把原数据转换到新的k维特征子空间
通过这个特征矩阵W,把原样本x转换到PCA的子空间上,得到一个新样本x^,。
x^,=xW
训练集与W点积后,把这个训练集转换到包括两个主成分的子空间上。然后,把子空间的数据可视化。

从以上图形可以看出,大部分数据沿PC1方向分布,而且可以线性划分,在可视化图形时,为便于标识点,这里采用了y_train标签信息。

我们用来6步来实现PCA,这个过程还是比较麻烦的,是否有更简单的方法呢?
有的,接下来我们介绍利用Scikit-learn中PCA类进行降维。

17.3 利用Scikit-learn进行主成分分析

我们将使用Scikit-learn中PCA对数据集进行预测处理,然后使用逻辑斯谛回归对转换后的数据进行分类,最后对数据进行可视化。
1)数据预处理

得到主成分数据:
array([ 0.37329648, 0.18818926, 0.10896791, 0.07724389, 0.06478595, 0.04592014, 0.03986936, 0.02521914, 0.02258181, 0.01830924, 0.01635336, 0.01284271, 0.00642076])

2)可视化主成分方差贡献率图

3)获取前2个主成分

5)利用回归模型对数据进行分类。

6)为了更好看到分类后情况,这里我们定义一个函数plot_decision_regions,通过这个函数对决策区域数据可视化。

7)把训练数据转换到前两个主成分轴后生成决策区域图形

对高维数据集进行降维除了PCA方法,还有线性判别分析(Linear Discriminant Analysis, LDA)、决策树、核主成分分析、SVD等等。

17.4 LDA 降维

LDA的基本概念与PCA类似,PCA是在数据集中找到方差最大的正交的主成分分量的轴。而LDA的目标是发现可以最优化分类的特征子空间。两者都是可以用于降维的线性转换方法,其中,PCA是无监督算法,LDA是监督算法。与PCA相比,LDA是一种更优越的用于分类的特征提取技术。
LDA的主要步骤:
(1)对d维数据集进行标准化处理(d为特征数量)
(2)对每一类别,计算d维的均值向量
(3)构造类间的散布矩阵S_B以及类内的散布矩阵S_W
(4)计算矩阵〖S_W〗^(-1) S_B的特征值所对应的特征向量,
(5)选取前k个特征值对应的特征向量,构造一个d x k维的转换矩阵W,其中特征向量以列的形式排列
(6)使用转换矩阵W将样本映射到新的特征子空间上.
以下还是以下葡萄酒数据为例,用代码实现以上各步:
(1)对d维数据集进行标准化处理

(2)对每一类别,计算d维的均值向量

运行结果
MV 1: [ 0.9259 -0.3091 0.2592 -0.7989 0.3039 0.9608 1.0515 -0.6306 0.5354 0.2209 0.4855 0.798 1.2017]

MV 2: [-0.8727 -0.3854 -0.4437 0.2481 -0.2409 -0.1059 0.0187 -0.0164 0.1095 -0.8796 0.4392 0.2776 -0.7016]

MV 3: [ 0.1637 0.8929 0.3249 0.5658 -0.01 -0.9499 -1.228 0.7436 -0.7652 0.979 -1.1698 -1.3007 -0.3912]
(3)构造类间的散布矩阵S_B以及类内的散布矩阵S_W
通过均值向量计算类内散布矩阵Sw:

通过累加各类别i的散布矩阵Si来计算:

运行结果
Within-class scatter matrix: 13x13

计算各类标样本数

运行结果为:
Class label distribution: [40 49 35]
由此看出,各类记录数不很均匀,为此,需要对SB进行归一化处理:

运行结果
Scaled within-class scatter matrix: 13x13

计算类间散布矩阵:

运行结果
Between-class scatter matrix: 13x13

(5)选取前k个特征值对应的特征向量,构造一个d x k维的转换矩阵W,其中特征向量以列的形式排列

求得广义特征值之后,按照降序对特征值排序

运行结果
Eigenvalues in decreasing order:

452.721581245
156.43636122
7.05575044266e-14
5.68434188608e-14
3.41129233161e-14
3.40797229523e-14
3.40797229523e-14
1.16775565372e-14
1.16775565372e-14
8.59477909861e-15
8.59477909861e-15
4.24523361436e-15
2.6858909629e-15
d x d维协方差矩阵的秩最大为d-1,得到两个非0的特征值。
与PCA一样,我们可视化各特征贡献率

运行结果

(6)使用转换矩阵W将样本映射到新的特征子空间上.
由上面两个新得到两个特征构成一个新矩阵

d x d维协方差矩阵的秩最大为d-1,得到两个非0的特征值。Matrix W:
[[-0.0662 -0.3797]
[ 0.0386 -0.2206]
[-0.0217 -0.3816]
[ 0.184 0.3018]
[-0.0034 0.0141]
[ 0.2326 0.0234]
[-0.7747 0.1869]
[-0.0811 0.0696]
[ 0.0875 0.1796]
[ 0.185 -0.284 ]
[-0.066 0.2349]
[-0.3805 0.073 ]
[-0.3285 -0.5971]]
将样本映射到新的特征空间

运行结果

17.5 利用Scikit-learn进行LDA分析

下面我们利用scikit-learn中对LDA类的实现
这里先定义一个函数,后面需要用到

对数据先LDA处理,然后用逻辑回归进行分类。

运行结果

还有几个点划分错误,下面通过正则化,效果将更好

运行结果

17.6使用核PCA降维

前面我们介绍了两种降维方法,PCA及LDA.这两种方法,如果用于线性不可分数据集上进行分类,效果往往不很理想,原因是通过他们无法把线性不可分数据集变为线性可分数据集。如果遇到线性不可分数据集(这样的数据集往往比较普遍),有什么好方法,既降维,又可把线性不可分数据集变为线性可分数据集?
在SVM中,我们了解到核函数的神奇,把可以通过把线性不可分的数据集映射到一个高维空间,变得线性可分。基于这点,如果我们在降维时也采用核技术是否也可以呢?可以的,这就是接下来我们要介绍的内容---核PCA.
核PCA=核技术+PCA,具体步骤如下:
(1)计算核矩阵,也就是计算任意两个训练样本。这里以向基核函数(RBF)为例
经向基函数核(又称高斯核)为:

得到以下矩阵:

(2)对核矩阵K进行中心化处理

其中,是n*n的矩阵,n=训练集样本数,中每个元素都等于.l_n中的每个元素都是1/n
(3)求核矩阵的特征向量,并按降序排列,提取前k个特征向量。
不同于标准PCA,这里的特征向量并不是主成分轴。
下面我们根据以上三个步骤,实现一个核PCA。借助SciPy和NumPy,其实实现核PCA很简单。

下面以一分离同心数据集为例,分别用PCA和核PCA对数据集进行处理,然后处理后的结果,具体请看以下代码及生成的图形:

这是典型线性不可数据集,现在我们分别用PCA及核PCA进行处理。
(1)用PCA处理,然后进行分类

(2)用核PCA处理,然后进行分类

(3)使用sklearn实现核PCA
源数据的图形为

这里通过核PCA把该数据变为线性可分数据集,实现代码如下:

17.7 SVD矩阵分解

(1)SVD奇异值分解的定义
假设有一个mxn矩阵,如果存在一个分解

其中U为的mxm酉矩阵,∑为mxn的半正定对角矩阵,除了对角元素不为0,其他元素都为0,并且对角元素是从大到小排列的,前面的元素比较大,后面的很多元素接近0。这些对角元素就是奇异值。V^T为V的共轭转置矩阵,且为nxn的酉矩阵。这样的分解称为的奇异值分解,对角线上的元素称为奇异值,U称为左奇异矩阵,V^T称为右奇异矩阵。
SVD在信息检索(隐性语义索引)、图像压缩、推荐系统、金融等领域都有应用。
(2)SVD奇异值分解与特征值分解的关系
特征值分解与SVD奇异值分解的目的都是提取一个矩阵最重要的特征。然而,特征值分解只适用于方阵,而SVD奇异值分解适用于任意的矩阵,不一定是方阵。

这里M^T M和MM^T都是方阵,UU^T和VV^T都是单位矩阵,V是M^T M的特征向量,U是MM^T的特征向量。
(3)SVD奇异值分解的作用和意义
奇异值分解最大的作用就是数据的降维,当然,还有其他很多的作用,这里主要讨论数据的降维,对于mxn的M矩阵,进行奇异值分解

取其前k个非零奇异值,可以还原原来的矩阵,即前k个非零奇异值对应的奇异向量代表了矩阵的主要特征。可以表示为

17.8 用Python实现SVD,并用于图像压缩
(1)首先读取一张图片(128*128*3):

(2)然后可以利用python的numpy库对彩色图像的3个通道进行SVD分解

(3)然后便可以根据需要压缩图像(丢弃分解出来的三个矩阵中的数据),利用的奇异值个数越少,则压缩的越厉害。下面来看一下不同程度压缩后,重构图像的清晰度:

(4)其中restore函数定义为

16.1 目的

回归分析(regression analysis)是确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。运用十分广泛,回归分析按照涉及的变量的多少,分为一元回归和多元回归分析;按照因变量的多少,可分为简单回归分析和多重回归分析;按照自变量和因变量之间的关系类型,可分为线性回归分析和非线性回归分析。如果在回归分析中,只包括一个自变量和一个因变量,且二者的关系可用一条直线近似表示,这种回归分析称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且自变量之间存在线性相关,则称为多重线性回归分析.

16.2 模型

16.3 问题

假设我们有一组某企业广告费与销售额之间的对应关系的实际数据:对这些数据可视化结果如下:

可视化代码如下:

如果该企业先增加广告费,如20万,那么它可能带来的销售额大概是多少?
解决这个问题的关键就是如何根据已有数据的总体趋势,拟合出一条直线,如图1.1中虚线所示,那么新给出的广告费就可预测其对应的销售额?如何拟合这条直线呢?

16.4 简单入手

假设我们要拟合的这条直线为一元一次直线,表达式为:
y=ax+b (1.1)
要拟合这条直线或确定这条直线,只要求出a和b即可。那如何得到a和b呢?
这条直线要满足什么条件才是最好的或最能体现这些样本的趋势?
如果能使预测值与实际值的差距最小,应该是一个不错的直线。
由此,想到最小二乘法,利用最小二乘法作为损失函数,然后通过使损失函数最小化来求参数a和b。

最损失函数的最小值,而且损失函数为凸函数,故可利用梯度为0来求出参数a和b
具体过程如下:

如此,a,b确定后,自然直线y=ax+b也就确定了,这样便可以根据新的x值,预测其y值了。

16.5、用Python解方程求出a和b

打印结果为:
所求直线为: y=1.980810*x+2.251599

16.6 用迭代方式求参数

以下利用迭代的方法求出参数a和b
直接通过解方程来求参数a和b,如果参数比较多,样本数也很多的情况下,计算量非常大,而且也不现实。因此,我们需要另辟蹊径!
还是以求函数y=x^2的最小值为例,我们可以通过其导数为0来求来求的是y最小的x值;我们也可以通过迭代的方式来求,先从某点开始,如x_0开始,然后沿梯度的方向,不断靠近最小值点,如下图所示:

为啥每次修改的值,都能往函数最小值那个方向前进呢?这里的奥秘在于,我们每次都是向函数的梯度的相反方向来修改。

什么是梯度呢?翻开大学高数课的课本,我们会发现梯度是一个向量,它指向函数值上升最快的方向。显然,梯度的反方向当然就是函数值下降最快的方向了。我们每次沿着梯度相反方向去修改的值,当然就能走到函数的最小值附近。

之所以是最小值附近而不是最小值那个点,是因为我们每次移动的步长不会那么恰到好处,有可能最后一次迭代走远了越过了最小值那个点。步长的选择是门手艺,如果选择小了,那么就会迭代很多轮才能走到最小值附近;如果选择大了,那可能就会越过最小值很远,收敛不到一个好的点上。

按照上面的讨论,我们就可以写出梯度下降算法的公式

打印结果:
参数b的值:2.44
参数a的值:1.95

通过迭代方法求得的参数a,b与通过梯度求得的参数进行比较,发现他们非常接近,有异曲同工之妙!
这种方法也可看作只有一个神经元的神经网络,并且没有激活函数的这种。

16.7 迭代中用矩阵代替循环

这里利用梯度下降更新参数时采用一个循环,循环一般比较耗费资源,如果有一千万个数据,将需要循环一千万次,这是不可接受的,那么我们是否能不用循环?
当然可以,如果用采用矩阵方式,需要进行如下操作:
 把输入X,Y变为矩阵;
 把模型变为矩阵或向量;
 把式(1.13)变为矩阵与向量的点乘
 把式(1.14)变为矩阵的累加
具体代码实现如下:
(1)定义一个线性回归类,在这个类中初始化两个参数,并定义几个函数。

(2)把输入变为10x1矩阵,如何运行以上函数

打印结果:
参数a的值:1.995
参数b的值:2.130
(3)把迭代过程可视化

16.8 用Tensorflow架构实现自动求导,求参数

运行结果
初始权重值w: [-0.99174666]
0 [ 3.35317731] [ 0.51469594]
100 [ 2.17306757] [ 0.62030703]
200 [ 2.14828968] [ 0.83054471]
300 [ 2.12670541] [ 1.01368761]
400 [ 2.10790277] [ 1.17322707]
500 [ 2.09152317] [ 1.31220567]
600 [ 2.07725477] [ 1.43327284]
700 [ 2.06482506] [ 1.53873765]
800 [ 2.05399752] [ 1.63060951]
900 [ 2.0445652] [ 1.71064162]
6.90384
最后权重值w: [ 2.03642535]
最后偏移量b: [ 1.77970874]

16.9 拓展

15.1 聚类概述

“物以类聚人以群分",现实生活中很多事物都存在类似现象,当然这些现象反映在数据上,就需要我们通过一定算法,找出这些类或族。聚类算法就是解决类似问题而提出的。
聚类就是按照某个特定标准(如距离准则)把一个数据集分割成不同的类或簇,使得同一个簇内的数据对象的相似性尽可能大,同时不在同一个簇中的数据对象的差异性也尽可能地大。即聚类后同一类的数据尽可能聚集到一起,不同数据尽量分离。
目前,有大量的聚类算法。而对于具体应用,聚类算法的选择取决于数据的类型、聚类的目的。如果聚类分析被用作描述或探查的工具,可以对同样的数据尝试多种算法,以发现数据可能揭示的结果。
主要的聚类算法可以划分为如下几类:划分方法、层次方法、基于密度的方法、基于网格的方法以及基于模型的方法
每一类中都存在着得到广泛应用的算法,例如:划分方法中的k-means聚类算法、层次方法中的层次聚类算法、基于模型方法中的高斯混合聚类算法等。
目前,聚类问题的研究不仅仅局限于上述的硬聚类,即每一个数据只能被归为一类,模糊聚类也是聚类分析中研究较为广泛的一个分支。模糊聚类通过隶 属函数来确定每个数据隶属于各个簇的程度,而不是将一个数据对象硬性地归类到某一簇中。目前已有很多关于模糊聚类的算法被提出,如著名的高斯混合聚类等。
下图演示了K-Means进行聚类的迭代过程:


下图为高斯混合聚类迭代过程:

15.2 k-means模型

算法步骤:
(1)首先我们选择一些类/组,并随机初始化它们各自的中心点。
(2)计算每个数据点到中心点的距离,数据点距离哪个中心点最近就划分到哪一类中。
(3)重新计算每一类中心点作为新的中心点,各中心点求每个类中的平均值。
(4)重复以上步骤,直到每一类中心在每次迭代后变化不大为止。也可以多次随机初始化中心点,然后选择运行结果最好的一个。

优点:
计算简便
缺点:
我们必须提前知道数据有多少类/组。

15.3 简单实例

假定我们有如下8个点:
A1(2, 10) A2(2, 5) A3(8, 4) A4(5, 8) A5(7, 5) A6(6, 4) A7(1, 2) A8(4, 9)
现希望分成3个聚类(即k=3)
初始化选择 A1(2, 10), A4(5, 8) ,A7(1, 2)为聚类中心点,假设两点距离定义为ρ(a, b) = |x2 – x1| + |y2 – y1| . (当然也可以定义为其它格式,如欧氏距离)
第一步:选择3个聚类中,分别为A1,A4,A7

这些点的分布图如下:

图1
第二步:计算各点到3个类中心的距离,那个点里类中心最近,就把这个样本点
划归到这个类。选定3个类中心(即:A1,A4,A7),如下图:
图2
对A1点,计算其到每个cluster 的距离
A1->class1 = |2-2|+|10-10}=0
A1->class2 = |2-5|+|10-8|=5
A1->class3 = |2-1|+|10-2|=9
因此A1 属于cluster1,如下表:
(
按照类似方法,算出各点到聚类中心的距离,然后按照最近原则,把样本点放在那个族中。如下表:

根据距离最短原则,样本点的第一次样本划分,如下图:
图3
第三步:求出各类中样本点的均值,并以此为类的中心。
cluster1只有1个点,因此A1为中心点
cluster2的中心点为 ( (8+5+7+6+4)/5,(4+8+5+4+9)/5 )=(6,6)。注意:这个点并非样本点。
cluster3的中心点为( (2+1)/2, (5+2)/2 )= (1.5, 3.5),
新族的具体请看下图中x点:

图4
第四步:计算各样本点到各聚类中心的距离,重复以上第二、第三步,把样本划分到新聚类中,如下图:
图5
持续迭代,直到前后两次迭代不发生变化为止,如下:
图6

15.4 简单实例用Python实现

(1)生成数据

(2)创建距离函数

(3)手工选择3个聚类中心

(4)创聚类函数

(5)运行

运行结果
(array([[3, 9],
[7, 4],
[1, 3]]), matrix([[ 0., 4.],
[ 2., 9.],
[ 1., 1.],
[ 0., 9.],
[ 1., 1.],
[ 1., 1.],
[ 2., 1.],
[ 0., 1.]]))
这个运行结果与图6的结果一致。

15.5 简单实例用sklearn实现

(1)导入需要的库或模块

(2)创建数据

(3)利用kmeans进行聚类,并把结果可视化

15.6 简单实例用Tensorflow实现

这里需要用到很多tensorflow函数,大家可参考:
https://www.cnblogs.com/wuzhitj/p/6648563.html
(1)导入需要的库,初始化参数

(2)创建数据集,并可视化三个族中心

(3)可视化样本

(4)计算各样本的到各族中心距离

(5)定义函数,更新各族中心坐标

(6)可视化迭代过程

迭代1次就到达最佳结果,看来是要tensorflow效果不错!

15.7 改进

由于 K-means 算法的分类结果会受到初始点的选取而有所区别,因此有提出这种算法的改进: K-means++。其实这个算法也只是对初始点的选择有改进而已,其他步骤都一样。初始质心选取的基本思路就是,初始的聚类中心之间的相互距离要尽可能的远。整个算法的过程如下:
下面结合一个简单的例子说明K-means++是如何选取初始聚类中心的。数据集中共有8个样本,分布以及对应序号如下图所示:
图7
假设经过图7的步骤一后6号点被选择为第一个初始聚类中心,那在进行步骤二时每个样本的D(x)和被选择为第二个聚类中心的概率如下表所示:

其中的P(x)就是每个样本被选为下一个聚类中心的概率。最后一行的Sum是概率P(x)的累加和,用于轮盘法选择出第二个聚类中心。方法是随机产生出一个0~1之间的随机数,判断它属于哪个区间,那么该区间对应的序号就是被选择出来的第二个聚类中心了。例如1号点的区间为[0,0.2),2号点的区间为[0.2, 0.525)。
从上表可以直观的看到第二个初始聚类中心是1号,2号,3号,4号中的一个的概率为0.9。而这4个点正好是离第一个初始聚类中心6号点较远的四个点。这也验证了K-means的改进思想:即离当前已有聚类中心较远的点有更大的概率被选为下一个聚类中心。可以看到,该例的K值取2是比较合适的。当K值大于2时,每个样本会有多个距离,需要取最小的那个距离作为D(x)。


本章利用Scrapy架构,实现一个爬虫任务,从一个网站爬取一些教师信息(如教师姓名、职称、简介),然后把爬取的信息分别存放到cvs文件、MySQL。
环境为python3.6

4.1 Scrapy 框架入门简介

4.1.1 Scrapy 框架

Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便。Scrapy 使用了 Twisted'twɪstɪd异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。

Scrapy架构图(其中绿线是数据流向)

Scrapy Engine(引擎):
负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
Scheduler(调度器):
它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。

Downloader(下载器):
负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,
Spider(爬虫):
它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),
Item Pipeline(管道):
它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.
Downloader Middlewares(下载中间件):
你可以当作是一个可以自定义扩展下载功能的组件。
Spider Middlewares(Spider中间件):
你可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)

4.1.2 Scrapy 流程

Scrapy运行流程大概如下:
引擎从调度器中取出一个链接(URL)用于接下来的抓取
引擎把URL封装成一个请求(Request)传给下载器
下载器把资源下载下来,并封装成应答包(Response)
爬虫解析Response
解析出实体(Item),则交给实体管道进行进一步的处理
解析出的是链接(URL),则把URL交给调度器等待抓取
详细流程如下:
1 引擎:Hi!Spider, 你要处理哪一个网站?
2 Spider:老大要我处理xxxx.com。
3 引擎:你把第一个需要处理的URL给我吧。
4 Spider:给你,第一个URL是xxxxxxx.com。
5 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
6 调度器:好的,正在处理你等一下。
7 引擎:Hi!调度器,把你处理好的request请求给我。
8 调度器:给你,这是我处理好的request
9 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
10 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
11 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
12 Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
13 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
14 管道  调度器:好的,现在就做!

4.1.3 Scrapy 开发一般步骤

1、新建项目 (scrapy startproject xxx):新建一个新的爬虫项目
2、明确目标 (编写items.py):明确你想要抓取的目标
3、制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页
4、存储内容 (pipelines.py):设计管道存储爬取内容
本实例的流程如下图

4.1.4.Scrapy安装

验证安装是否成功

4.2入门案例

主要步骤:
1、创建一个Scrapy项目
2、定义提取的结构化数据(Item)
3、编写爬取网站的 Spider 并提取出结构化数据(Item)
4、编写 Item Pipelines 来存储提取到的Item(即结构化数据)

4.2.1 创建项目

新建项目(scrapy my_project)
在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的项目目录中,运行下列命令:

其中, my_project 为项目名称,可以看到将会创建一个 my_project 文件夹,目录结构大致如下:

下面来简单介绍一下各个主要文件的作用:
scrapy.cfg 是爬虫执行的入口文件。当输入“scrapy crawl”命令让爬虫开始工作时,首先会读取该文件中的配置项内容。
my_project/items.py 文件定义了爬虫抓取下来的数据,是以何种组织方式存储信息的。比如爬虫抓取的结果可以是标题字符串,也可以是结构化的JSON对象,或者是一张图片对应的字节流,items.py就是用来定义结构化对象中的属性名。
my_project/pipelines.py文件定义了信息的保存方式。爬虫抓取的内容,存放在内存对象中,如何保存这些信息,用户可以定义多种方式,比如写入文件、存入DB或者直接在控制台输出。Scrapy会采用管道(pipeline)方式,把内存中的信息依次交给每个管道文件。
my_project/settings.py 文件保存了爬虫运行时所依赖的配置信息。比如用户定义了两个pipelines.py文件,希望把抓取的内容先写入DB,再输出到控制台,那么就可以在setttings.py文件中定义ITEM_PIPELINES属性,其值分别给出了两个管道文件的文件名。比如:

my_project/spiders/ 是用来存放具体爬虫文件的目录。爬虫文件需要用户手动创建,在该目录下可以同时存在多个爬虫文件。

【备注】为保证输出格式为json文件也支持中文,在settings.py添加如下语句:

4.2.2 明确目标

我们打算抓取:http://www.itcast.cn/channel/teacher.shtml 网站里的所有讲师的姓名、职称和个人信息。打开my_project目录下的items.py
Item 定义结构化数据字段,用来保存爬取到的数据,有点像Python中的dict,但是提供了一些额外的保护减少错误。
可以通过创建一个 scrapy.Item 类, 并且定义类型为
scrapy.Field的类属性来定义一个Item。接下来,创建一个ItcastItem 类,和构建item模型(model)。

4.2.3 制作爬虫

主要编辑spiders/itcastSpider.py
爬虫功能要分两步:
1. 爬数据
在当前目录下输入命令,将在my_project /spider目录下创建一个名为itcast的爬虫,并指定爬取域的范围:

打开 my_project /spider目录里的 itcast.py,默认增加了下列代码:

其实也可以由我们自行创建itcast.py并编写上面的代码,只不过使用命令可以免去编写固定代码的麻烦要建立一个Spider, 你必须用scrapy.Spider类创建一个子类,并确定了三个强制的属性 和 一个方法。
name = "" :这个爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。
allow_domains = [] 是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略。
start_urls = () :爬取的URL元祖/列表。爬虫从这里开始抓取数据,所以,第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。
parse(self, response) :解析的方法,每个初始URL完成下载后将被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一参数,主要作用如下:
负责解析返回的网页数据(response.body),提取结构化数据(生成item)
生成需要下一页的URL请求。
将start_urls的值修改为需要爬取的第一个url

修改parse()方法

然后运行一下看看,在my_project目录下(含文件scrapy.cfg所在的目录)执行:

2. 取数据
爬取整个网页完毕,接下来的就是的取过程了,首先观察页面源码:

是不是一目了然?直接上XPath开始提取数据吧。
我们之前在my_project /items.py 里定义了一个ItcastItem类。 这里引入进来

然后将我们得到的数据封装到一个 ItcastItem 对象中,可以保存每个老师的属性,修改itcast.py文件如下。

【备注】这里补充一点xpath有关内容,更多内容大家可以通过google或百度查询
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
下面列出了最有用的路径表达式:

3. 运行
在scrapy.cfg 所在文件的目录下,运行如下命令

运行完成后,在当前目录下,将生成一个文件:teachers.csv

查看该文件:

4、修改settles.py文件
一些变量可存放在settles.py文件中,把数据库的连接信息存放在该文件,便于以后维护管理

5、利用pipeline,把数据导入mysql数据库
pipeline.py文件定义数据存储的方式,此处定义数据存储的逻辑,可以将数据存储加载到MySQL数据库,MongoDB数据库,文件,CSV,Excel等存储介质中,如下以存储载CSV为例。
编辑pipeline.py文件

6、修改settles.py文件
为说明通过pipeline导入数据程序,在文件settings.py后,添加如下一行:

7、运行以下命令

8、查询结果
csv文件内容:

查看数据库对应表部分记录:


如果你对爬虫感兴趣,但没有相关基础,这个实例就比较适合你。本实例非常简单,特别适合入门。本实例主要步骤如下本实例爬取一个有关教学研究人员网站 WISE(http://www.wise.xmu.edu.cn/people/faculty),提取这个页面上所有老师的姓名和个人主页的链接,然后简单解析相关标签(tag),然后把解析出来的内容存放在文件和数据库中。
讲完这个实例后面还有一个更高级的爬虫实例,使用Scrapy架构爬取网站内容,这个架构分布式、平行处理等我们就无需做过多配置,系统自动为我们配置好。
接下来,我们还是从一个简单实例开始吧。

3.1下载网页

使用requests下载网页,它是第三方库

把需要的网页内容提取出来:

到这一步我们已经获取了网页的源代码。具体源 代码是什么样的呢?右键,点击“查看源文件”或者“查看源”就可以看到。

3.2 解析网页

从源代码里面找。找到的我们需要的信息如下:

解析使用bs4模块,该模块有最常用的几个BeautifulSoup对象的方法(method)。我们使用的这几个方法,主要是通过HTML的标签和标签里面的参数来定位,然后用特定方法(method)提取数据。

使用了BeautifulSoup对象的find方法。这个方法的意思是找到带有‘div’这个标签并且参数包含" class = 'people_list' "的HTML代码。如果有多个的话,find方法就取第一个。那么如果有多个呢?可以使用find_all,现在我们要取出所有的“a”标签里面的内容:

显示结果
'助理教授'

这里我们使用find_all方法取出所有标签为“a”并且参数包含“ target = ‘_blank‘ ”的代码,返回一个列表。“a”标签里面的“href”参数是我们需要的老师个人主页的信息,而标签里面的文字是老师的姓名

运行结果:

3.3 拓展一

通过源码分析,可知教师的姓名,职位,邮箱等信息包含在td标签内,接下来我们解析源码并把解析结果保存到cvs文件中。具体步骤如下:
1、在td标签里,包含教师的姓名,职位,邮箱等信息

打印结果为
327
查看前几行数据

['敖萌幪',
'助理教授',
'mengmengao@xmu.edu.cn',
'Bowers, Roslyn',
'英语教师',
'bowers.roslyn@yahoo.com',
'Brown, Katherine',
'英语教师',
'kbrownanne@yahoo.com']
把数据保存到cvs文件中

3.4 拓展二

取出所以的td的内容,并按姓名,职位,邮箱格式写入数据库,采用mysql数据库,连接用pymysql,具体内容如下:

数据表创建成功
数据导入成功
查看数据库部分记录:

本文参考:https://zhuanlan.zhihu.com/p/21377121

26.1交通标志识别简介

26.1.1 交通标记易识特性

由于交通标志采用特定的文字、图形和符号,在一定范围内具有标准、醒目、信息明确的特性,一直是图像识别技术在交通领域应用的首选。从图像识别技术诞生之日起,交通标志识别的算法和模型就一直受到广泛的关注,也让这一技术发展相对成熟,并应用到自动驾驶领域中来。

图1 交通标志识别技术
我国的交通标志一共有一百余种,按类别可分为黄底黑边的警告标志、白底红圈的禁令标志、蓝底白字的指示标志,形状上以三角形、圆形和矩形为主。明确的形状和颜色区分、有限的标志数量,都为图像识别提供了一个相对稳定的应用环境。

图2 我国的交通标志

26.1.2交通标志识别技术的原理

利用图像识别技术的交通标志识别系统一般分为以下几个工作步骤:

图3 图像识别的步骤
1. 图像预处理:
在实际的交通场景中,由于运动抖动、自然光、天气条件等各种因素的影响,不可避免的会在采集的图像中引入一定程度的干扰和噪声,所以首先需要将这些不利因素消除,对采集到的图像进行一些预处理。通过图像均衡、图像增强和图像去噪等算法,将图像的光线均衡,突出关键信息。这一步基本和美图秀秀中的那些工具类似,都是将图像变得清晰、易辨认。

图4 对图像进行预处理,去除噪声、提高亮度和对比度
2. 交通标志分割:
预处理后的图像仍然包含很多信息,交通标志在其中只有很小的一个区域,为了减小处理的数据量,加快处理速度,一般都会先将交通标志的区域检测出来,再去判断这个区域中的交通标志的具体含义。交通标志在颜色和形状上都有一定的特殊性,并可按照下图进行一定程度的分类,所以一般通过这两个特征去检测交通标志。

图5 交通标志按颜色和形状分类
2.1颜色分割:
颜色按照国际标准可划分为RGB、HSV、HSI等颜色空间,并进行量化分析,以RGB空间为例,将颜色按照红色、绿色、蓝色三种颜色进行分割,通过给定交通标志牌中常用的蓝色、黄色、红色的色度坐标范围,即可过滤掉与之不相关的颜色信息,快速检测到交通标志牌。

图 6 通过RGB色彩空间处理,快速定位红色区域
2.2形状分割:
仅仅检测颜色显然又是不够的,由于光照、背景色的影响和干扰,还需要在颜色检测结果的基础上对相应区域进行形状检测。交通标志具有边缘清晰、形状简单易辨认的特点。这些特征在排除颜色影响后的灰度图像中更加明显,因此通过一定的边缘检测算子去判断图像像素中出现的灰度阶跃变化,一般就能较为准确的检测出交通标志的形状和轮廓特征。

图7 对检测区域进行灰度处理,再通过灰度阶跃检测其形状边界
3. 交通标志特征提取
在图像检测完成以后,图像中基本就只剩下了交通标志的关键信息,这些信息简单直观,但计算机依然不会知道这些信息的具体含义,这时候需要再进一步对这些图像特征进行提取和比对,才能对具体的信息进行识别。
图像的关键特征,是识别具体信息的关键因素,特征的好坏直接决定了识别的准确度。一般说来这些关键特征需要具有可区分性、简约性和抗干扰等几个要素,可区分性即不同标志的特征要具有足够的差异性,简约性是在保证可区分性的前提下用尽量少的数据表示图像特征,这可以保证检测的速度和效率,抗干扰度即图像特征信息要保证尽量少的会被噪声、自然光和图像畸变影响。
在交通标志识别上,一般会提取颜色特征、线条变化特征、矩特征、灰度直方图统计特征等等,并会在事先维护一个足够样本数量的特征库,包含现有交通标志的图像特征信息。在识别的时候将采集到的图像的特征提取出来与数据库中的条件进行比对,即可判断出该交通标志的实际意义。
4. 识别结果匹配
目前有多种方法实现图像特征与特征库数据的比对,最为简单直接的方式是模板匹配:即在特征库中将不同交通标志的特征参数规定为某些特定的参数,当所采集图像的特征参数在某个范围内,就判断是这个交通标志信息。但由于图像在采集的时候难免发生形状畸变、颜色失真等误差,在实际使用场景中用模板匹配来识别的成功率和准确度并不是特别高,即便优化了图像处理算法,也还有很多局限。

图 8 通过匹配特征库信息识别标志
近些年机器学习技术的发展,让图像识别也有了很大的变化,通过设定一些简单的判断条件,并在特征库中加入各种形态和场景下的学习样本,让系统不断加深交通标志识别的认知和识别概率。机器学习让识别不再依靠具体固定的参数,而是通过一系列的条件判断让系统找到概率最大的目标,以此提升识别的准确度和灵活性。这一技术在目前成为研究的热点,并有效提高了图像识别的准确率。

图 9 机器学习在图像识别中的应用

26.1.3 总结

交通标志识别是图像识别技术在自动驾驶领域较为成功的应用,其情景相对简单固定,识别准确度和成功率都让人满意。现在自动驾驶中摄像头识别车辆、行人、自行车、车道线等其他目标的工作原理基本和交通标志识别类似,只是针对不同的对象所用的的算法和模型也会进行一定的调整和优化,并维护一个更为多样的样本学习库。Mobileye在自动驾驶摄像头领域已经耕耘了17年,其算法集成优化、样本库丰富度、识别精确度都处于绝对领先,也为自动驾驶的普及带来了巨大的福音。

26.2 交通标志识别常用模型

1、LeNet(X)
是交通标志识别中非常经典的算法结构,其结构如下:

但是要实现交通标志识别还需要对该算法的初始结构进行调整。比如输出层的分类数调整为43;Subsampling layers 转为max pooling layers;增加Dropout 层,初始设置keep_prob=0.9;激活函数采用RELU。
改进后的架构流程如下表所示:

2、AlexNet
AlexNet是2012年发表的一个经典网络,在当年取得了ImageNet的最好成绩。
这是AlexNet的网络结构图:

网络共有8层,其中前5层是卷积层,后边3层是全连接层,在每一个卷积层中包含了激励函数RELU以及局部响应归一化(LRN)处理,然后再经过池化(max pooling),最后的一个全连接层的输出是具有1000个输出的softmax层,最后的优化目标是最大化平均的多元逻辑回归。
3、其它多层神经网络
36.3 实例1(使用多层神经网络)
这个Project的目的是利用神经卷积网路(CNN)来分类(classify)常见的交通标志。 CNN 在读图领域已经全面超过了传统的机器学习方法(SVC, OpenCV)。大量的资料是深度学习准确性的保证, 在资料不够的情况下也可以人为的对原有资料进行小改动从而来提高识别的准确度。汇入必要的软体包(pickle, numpy, cv2, matplotlib, sklearn, tensorflow, Keras)
这里使用python3.6,TensorFlow1.6 等,使用GPU比使用CPU快约100倍!
1)导入需要的模块或库

【注意】如果环境已安装keras,可以直接使用,导入方式可改为:

资料来源: 和大部分的机器学习的要求一样, CNN需要大量有label的资料,German Traffic Sign Dataset提供了对于这个project的研究所需的数据,本章数据集下载

2)导入数据

打印结果
Number of training examples = 39209
Number of testing examples = 12630
Image data shape = (32, 32, 3)
Number of classes = 43

3)探索数据
从上面我们可以看到有39209个用作训练的影象 和 12630个testing data。 39209张照片对于训练CNN来说是不够的(100000张以上是比较理想的资料量), 所以之后要加入data augment 的模组来人为增加资料。每张影象的大小是是32×32 并且有3个通道。总共有43个不同的label。我们也可以把每个label对应的图片随机选择一张画出来。

4)查看各种标志的分布情况

从上图可以看到这43个类别的资料量的分配是很不均匀的。这个会给CNN带来bias(偏见):CNN会更倾向于预测在training data里出现频率多的那些分类。

5)数据预处理
资料前期处理 根据这篇论文[Sermanet, LeCun], 把RGB的照片转化成YUV(除了RGB模型外,还有一种广泛采用的模型,称为YUV模型,又被称为亮度-色度模型(Luma-ChromaModel)。它是通过数学转换,将RGB三通道转换为一个代表亮度的通道(Y,又称为Luma),和两个代表色度的通道(UV,并称为Chroma)来记录图像的模型) 然后只选择Y通道的图片可以在不影响精度的同时减少资料计算量。然后每张图片都转化成以0为平均值, 以1为标准差的阵列。

人为添加数据(data augment) 对于图片来时, 一定程度的旋转, 上下左右移动, 放大或者缩小都应该不会影响它的标签。 虽然图片数据已经完全不一样了, 我们肉眼还是能够识别的出来, 这能够在增加数据量的同时帮助CNN 总结(generalize). Keras 有个很方便的函数ImageDataGenerator(rotation_range=15., zoom_range=0.2, width_shift_range=0.1, height_shift_range=0.1) 可以实现这个, 这个函数可以设置 旋转的角度rotation_range, 放大或缩小的倍数zoom_range, 左右移动的比例width_shift_range 和上下移动的比例height_shift_range , 随机在区间内改动原来的照片并产生无数新的照片, 下面我选择一张作为示范:

6)搭建神经网络
搭建CNN 每一层的神经网络都加了dropout 来防止overfitting。这个CNN的特点是把两层conv的output做了一个合成:fc0 = tf.concat([flatten(drop1), flatten(drop2)],1 ) 然后再连接到fully_connected layer 和output layer(43 classes)。文献中说这样做的好处是“the classifier is explicitly provided both the local “motifs” (learned by conv1) and the more “global” shapes and structure (learned by conv2) found in the features.” 我的理解是: CNN 能够在图片的局部和整体都能作为判断的依据,从而提高准确率

【备注】如果lr大小,如小于0.0001可能需要花费更多训练时间,如果太大,如0.1可能很难提高精度,所以选择合适lr很重要。
定义超参数

【备注】大家可以根据情况,增加迭代次数,批量大小,批量一般不宜过大,否则将迅速增加训练时间。
7)训练CNN模型

【备注】keep_prob参数控制dropout的比例,这个参数很重要,大家可以修改该参数,看它对精度的影响。
Training...

EPOCH 1 ...
Training Accuracy = 0.224

EPOCH 2 ...
Training Accuracy = 0.339

EPOCH 3 ...
Training Accuracy = 0.400

EPOCH 4 ...
Training Accuracy = 0.452

EPOCH 5 ...
Training Accuracy = 0.508

EPOCH 6 ...
Training Accuracy = 0.548

EPOCH 7 ...
Training Accuracy = 0.587

EPOCH 8 ...
Training Accuracy = 0.609

EPOCH 9 ...
Training Accuracy = 0.658

.......................

EPOCH 90 ...
Training Accuracy = 0.971

EPOCH 91 ...
Training Accuracy = 0.968

EPOCH 92 ...
Training Accuracy = 0.971

EPOCH 93 ...
Training Accuracy = 0.975

EPOCH 94 ...
Training Accuracy = 0.965

EPOCH 95 ...
Training Accuracy = 0.977

EPOCH 96 ...
Training Accuracy = 0.967

EPOCH 97 ...
Training Accuracy = 0.976

EPOCH 98 ...
Training Accuracy = 0.976

EPOCH 99 ...
Training Accuracy = 0.976

EPOCH 100 ...
Training Accuracy = 0.977
【说明】因时间关系,我这里只训练100次,看来精度也还可以,如果增加迭代次数,可以达到99%左右
8)用测试数据验证模型

26.3 实例2(使用LeNet神经网络)

待续