作者归档:feiguyun


数据预处理在解决深度学习问题的过程中,往往需要花费大量的时间和精力。 数据处理的质量对训练神经网络来说十分重要,良好的数据处理不仅会加速模型训练, 更会提高模型性能。为解决这一问题,PyTorch提供了几个高效便捷的工具, 以便使用者进行数据处理或增强等操作,同时可通过并行化加速数据加载。
【说明】本文使用的cat-dog数据集,请到书《Python深度学习基于PyTorch》下载代码及数据部分下载。
数据集存放大致有以下两种方式:
(1)所有数据集放在一个目录下,文件名上附有标签名,数据集存放格式如下: root/cat_dog/cat.01.jpg
root/cat_dog/cat.02.jpg
........................
root/cat_dog/dog.01.jpg
root/cat_dog/dog.02.jpg
......................
(2)不同类别的数据集放在不同目录下,目录名就是标签,数据集存放格式如下:
root/ants/xxx.png
root/ants/xxy.jpeg
root/ants/xxz.png
................
root/bees/123.jpg
root/bees/nsdf3.png
root/bees/asd932_.png
..................

1.1 对第1种数据集的处理步骤

(1)生成包含各文件名的列表(List)
(2)定义Dataset的一个子类,该子类需要继承Dataset类,查看Dataset类的源码
(3)重写父类Dataset中的两个魔法方法: 一个是: __lent__(self),其功能是len(Dataset),返回Dataset的样本数。 另一个是__getitem__(self,index),其功能假设索引为i,使Dataset[i]返回第i个样本。
(4)使用torch.utils.data.DataLoader加载数据集Dataset.

1.2 实例详解

以下以cat-dog数据集为例,说明如何实现自定义数据集的加载。

1.2.1 数据集结构

所有数据集在cat-dog目录下:
.\cat_dog\cat.01.jpg
.\cat_dog\cat.02.jpg
.\cat_dog\cat.03.jpg
....................
.\cat_dog\dog.01.jpg
.\cat_dog\dog.02.jpg
....................

1.2.2 导入需要用到的模块

1.2.3定义加载自定义数据的类

1.2.4 实例化类

<class 'PIL.Image.Image'>

1.2.5 查看图像形状

img的形状(500, 374),label的值0
img的形状(300, 280),label的值0
img的形状(489, 499),label的值0
img的形状(431, 410),label的值0
img的形状(300, 224),label的值0

从上面返回样本的形状来看:
(1)每张图片的大小不一样,如果需要取batch训练的神经网络来说很不友好。
(2)返回样本的数值较大,未归一化至[-1, 1]
为此需要对img进行转换,如何转换?只要使用torchvision中的transforms即可

1.2.6 对图像数据进行处理

这里使用torchvision中的transforms模块

1.2.7查看处理后的数据

图像img的形状torch.Size([3, 224, 224]),标签label的值0
图像数据预处理后:
tensor([[[ 0.9059, 0.9137, 0.9137, ..., 0.9451, 0.9451, 0.9451],
[ 0.9059, 0.9137, 0.9137, ..., 0.9451, 0.9451, 0.9451],
[ 0.9059, 0.9137, 0.9137, ..., 0.9529, 0.9529, 0.9529],
...,
[-0.4824, -0.5294, -0.5373, ..., -0.9216, -0.9294, -0.9451],
[-0.4980, -0.5529, -0.5608, ..., -0.9294, -0.9373, -0.9529],
[-0.4980, -0.5529, -0.5686, ..., -0.9529, -0.9608, -0.9608]],

[[ 0.5686, 0.5765, 0.5765, ..., 0.7961, 0.7882, 0.7882],
[ 0.5686, 0.5765, 0.5765, ..., 0.7961, 0.7882, 0.7882],
[ 0.5686, 0.5765, 0.5765, ..., 0.8039, 0.7961, 0.7961],
...,
[-0.6078, -0.6471, -0.6549, ..., -0.9137, -0.9216, -0.9373],
[-0.6157, -0.6706, -0.6784, ..., -0.9216, -0.9294, -0.9451],
[-0.6157, -0.6706, -0.6863, ..., -0.9451, -0.9529, -0.9529]],

[[-0.0510, -0.0431, -0.0431, ..., 0.2078, 0.2157, 0.2157],
[-0.0510, -0.0431, -0.0431, ..., 0.2078, 0.2157, 0.2157],
[-0.0510, -0.0431, -0.0431, ..., 0.2157, 0.2235, 0.2235],
...,
[-0.9529, -0.9843, -0.9922, ..., -0.9529, -0.9608, -0.9765],
[-0.9686, -0.9922, -1.0000, ..., -0.9608, -0.9686, -0.9843],
[-0.9686, -0.9922, -1.0000, ..., -0.9843, -0.9922, -0.9922]]])

由此可知,数据已标准化、规范化。

1.2.8对数据集进行批量加载

使用DataLoader模块,对数据集dataset进行批量加载

torch.Size([4, 3, 224, 224]) torch.Size([4])
torch.Size([4, 3, 224, 224]) torch.Size([4])
torch.Size([4, 3, 224, 224]) torch.Size([4])
torch.Size([4, 3, 224, 224]) torch.Size([4])
torch.Size([4, 3, 224, 224]) torch.Size([4])
torch.Size([4, 3, 224, 224]) torch.Size([4])
torch.Size([4, 3, 224, 224]) torch.Size([4])
torch.Size([4, 3, 224, 224]) torch.Size([4])
torch.Size([4, 3, 224, 224]) torch.Size([4])
torch.Size([4, 3, 224, 224]) torch.Size([4])
torch.Size([2, 3, 224, 224]) torch.Size([2])

1.2.9随机查看一个批次的图像

2 对第2种数据集的处理

处理这种情况比较简单,可分为2步:
(1)使用datasets.ImageFolder读取、处理图像。
(2)使用.data.DataLoader批量加载数据集,示例如下:

6.1 概述

从2013 google推出最初的一种Word Embedding(即word2vec)后,其发展非常迅速,尤其近些年对Word Embedding的应用和拓展,取得了非常大的进步。
Embedding的起源,应该追溯到Word2Vec,Word2Vec是一种Word Embedding。由于Word Embedding克服了传统整数向量、One-Hot向量的耗资源、对语句表现能力差等不足,Word Embedding在最近几年发展和创新不断,从最初的Word Embedding,已发展成Sequence Embedding、catategorial data Embedding、Graph Embedding等等。应用也从当初的自然语言处理(NLP)到传统机器学习、自然语言处理、推荐系统、广告、搜索排序等领域,并大大提升了这些应用的性能。
Embedding尤其在NLP方面,近些年取得巨大进步。可以说是最近几年在NLP领域最大的突破就在Word Embedding方面。如将替换RNN\LSTM的Transformer或Transformer-XL,在很多下游复杂任务上已超越人类平均水平的BERT、XLNET模型等等。而这些算法或模型的应用开始在推荐、传统机器学习、视觉处理、NLP方面开始爆发!难怪有人说Embedding时代已来临!
以下我用图形这种简单明了的方式,简单介绍一下Embedding的历史及最近几年的突破性进展,希望能给大家进一步学习提供参考。

6.2Embedding在Entity领域的拓展

Entity Embedding是推荐系统、计算广告领域最近非常流行的做法,是从word2vec等一路发展而来的Embedding技术的最新延伸;
Entity Embedding中的Categorical Variable Embedding已成为贯通传统机器学习与深度学习的桥梁。

6.3 Embedding在NLP领域的最新进展

贪心学院2020-2-15至2020-2-22日5次公开课

6.4 在Graph领域的拓展

Graph Embedding是推荐系统、计算广告领域最近非常流行的做法,是从word2vec等一路发展而来的Embedding技术的最新延伸;
Graph Embedding已经有很多大厂将Graph Embedding应用于实践后取得了非常不错的线上效果。

6.5 Embedding 应用到机器学习中

Embedding的起源和火爆都是在NLP中的,经典的word2vec都是在做word embedding这件事情,而真正首先在结构数据探索embedding的是在kaggle上的《Rossmann Store Sales》中的用简单特征工程取得第3名的解决方案(前两名为该领域的专业人士,采用了非常复杂的特征工程),作者在比赛完后为此方法整理一篇论文放在了arXiv,文章名:《Entity Embeddings of Categorical Variables》。

6.6Embedding在推荐领域的超级应用

1、[Item2Vec] Item2Vec-Neural Item Embedding for Collaborative Filtering (Microsoft 2016)
这篇论文是微软将word2vec应用于推荐领域的一篇实用性很强的文章。该文的方法简单易用,可以说极大拓展了word2vec的应用范围,使其从NLP领域直接扩展到推荐、广告、搜索等任何可以生成sequence的领域。
2、[Airbnb Embedding] Real-time Personalization using Embeddings for Search Ranking at Airbnb (Airbnb 2018)
Airbnb的这篇论文是KDD 2018的best paper,在工程领域的影响力很大,也已经有很多人对其进行了解读。简单来说,Airbnb对其用户和房源进行embedding之后,将其应用于搜索推荐系统,获得了实效性和准确度的较大提升。文中的重点在于embedding方法与业务模式的结合,可以说是一篇应用word2vec思想于公司业务的典范。
3、 [Airbnb Embedding] Real-time Personalization using Embeddings for Search Ranking at Airbnb (Airbnb 2018)
Airbnb的这篇论文是KDD 2018的best paper,在工程领域的影响力很大,也已经有很多人对其进行了解读。简单来说,Airbnb对其用户和房源进行embedding之后,将其应用于搜索推荐系统,获得了实效性和准确度的较大提升。文中的重点在于embedding方法与业务模式的结合,可以说是一篇应用word2vec思想于公司业务的典范。
4、[Alibaba Embedding] Billion-scale Commodity Embedding for E-commerce Recommendation in Alibaba (Alibaba 2018)
阿里巴巴在KDD 2018上发表的这篇论文是对Graph Embedding非常成功的应用。从中可以非常明显的看出从一个原型模型出发,在实践中逐渐改造,最终实现其工程目标的过程。这个原型模型就是上面提到的DeepWalk,阿里通过引入side information解决embedding问题非常棘手的冷启动问题,并针对不同side information进行了进一步的改造形成了最终的解决方案。
5、Behavior Sequence Transformer for E-commerce Recommendation in Alibaba(阿里2019)
近日,阿里巴巴搜索推荐事业部发布了一项新研究,首次使用强大的 Transformer 模型捕获用户行为序列的序列信号,供电子商务场景的推荐系统使用。该模型已经部署在淘宝线上,实验结果表明,与两个基准线对比,在线点击率(CTR)均有显著提高。

6.7 Embedding协助NLP极大提升性能

从简单的 Word2Vec,ELMo,GPT,BERT,XLNet到ALBERT, 这几乎是NLP过去10年最为颠覆性的成果。
1、BERT
BERT是一种基于Transformer Encoder来构建的一种模型,它整个的架构其实是基于DAE(Denoising Autoencoder)的,这部分在BERT文章里叫作Masked Lanauge Model(MLM)。MLM并不是严格意义上的语言模型,因为整个训练过程并不是利用语言模型方式来训练的。BERT随机把一些单词通过MASK标签来代替,并接着去预测被MASK的这个单词,过程其实就是DAE的过程。BERT有两种主要训练好的模型,分别是BERT-Small和BERT-Large, 其中BERT-Large使用了12层的Encoder结构。 整个的模型具有非常多的参数。
BERT在2018年提出,当时引起了爆炸式的反应,因为从效果上来讲刷新了非常多的记录,之后基本上开启了这个领域的飞速的发展。
2、XLNET
XLNet震惊了NLP领域,这种语言建模的新方法在20个NLP任务上的表现优于强大的BERT,并且在18个任务中获得了最先进的结果。
3、ALBERT
谷歌Lab近日发布了一个新的预训练模型"ALBERT"全面在SQuAD 2.0、GLUE、RACE等任务上超越了BERT、XLNet再次刷新了排行榜!ALBERT是一种轻量版本的BERT,利用更好的参数来训练模型,但是效果却反而得到了很大提升!ALBERT的核心思想是采用了两种减少模型参数的方法,比BERT占用的内存空间小很多,同时极大提升了训练速度,更重要的是效果上也有很大的提升!

6.8 Embedding表示为何如此重要?

1、它体量小,但能量大(是维度较少,但为连续性数值的向量);
2、它能在循环中不断学习(学习中能学到很多数据中很多内在规则或特性),下图为在训练完之后,学到的一些特性。

通过学习,如国王这个词,通过学习,能获取性能、王室等重要信息,而不仅仅是这个词的数字表示。


在《XGBoost与神经网络谁更牛?》文章,基于相同数据,对XGBoost和神经网络(NN)两种方法进行横行比较。XGBoost是属于Tree Model,只把分类特征进行数字化。对神经网络(NN)先把分类特征数值化,然后转换为One-hot。结果两个算法的测试结果(损失值)如下:
表3-1两种不同算法测试结果

《XGBoost与神经网络谁更牛?》基于相同数据集用不同算法进行比较,本章将从纵向进行比较,即基于相同数据集,相同算法(都是神经网络NN),但特征处理方式不同。一种是《XGBoost与神经网络谁更牛?》的方法,另外一种对分类特征采用Embedding方法,把分类特征转换为向量,并且这些向量的值会随着模型的迭代而自动更新。这两种方法比较的结果如表3-2所示:
表3-2 同一算法不同特征处理方式的测试结果

训练及测试数据没有打乱,其中测试数据是最新原数据的10%。
从表3-2可以看出,使用EE处理分类特征的方法性能远好于不使用EE的神经网络,使用EE有利于充分发挥NN的潜能,提升NN在实现传统机器学习任务的性能。
EE处理方法为何能大大提升模型的性能?使用EE处理特征有哪些优势,Embedding方法是否还适合于处理其它特征?如连续特征、时许特征、图(graph)特征等。这些问题后续将陆续进行说明。接下来我们将用Keras或TensorFlow的Embeding层处理分类特征。

3.1 Embedding简介

神经网络、深度学习的应用越来越广泛,从计算机视觉到自然语言处理和时间序列预测。在这些领域都取得不错成绩,有很多落地项目性能已超过人的平均水平。不但有较好的性能,特征工程方面更是实现了特征工程的自动化。
特征工程是传统机器学习的核心任务,模型性能如何很大程度取决特征工程处理方法,由此,要得到理想的特征工程往往需要很多业务和技术方面的技巧,因此有“特征工程是一门艺术”的说法,这也从一个侧面说明特征工程的门槛比较高,不利于普及和推广。不过这种情况,近些年正在改变。为了解决这一大瓶颈,人们开始使用神经网络或深度学习方法处理传统机器学习任务,特征工程方法使用Embedding 方法,把离散变量转变为较低维的向量,通过这种方式,我们可以将神经网络,深度学习用于更广泛的领域。
目前Embedding已广泛使用在自然语言处理(NLP)、结构化数据、图形数据等处理方面。在NLP 中常用的 Word Embedding ,对结构化数据使用 Entity Embedding,对图形数据采用Graph Embedding。这些内容后续我们将陆续介绍。这里先介绍如何用keras或TensorFlow实现分类特征的Embedding。

3.1.1 Keras.Embedding格式

keras的Embedding层格式如下:

Keras提供了一个嵌入层(Embedding layer),可用于处理文本数据、分类数据等的神经网络。它要求输入数据进行整数编码,以便每个单词或类别由唯一的整数表示。嵌入层使用随机权重初始化,并将学习所有数据集中词或类别的表示。这层只能作为模型的第1层。
【参数说明】
 input_dim: int > 0。词汇表大小或类别总数, 即,最大整数索引(index) + 1。
 output_dim: int >= 0。词向量的维度。
 embeddings_initializer: embeddings 矩阵的初始化方法 (详见 https://keras.io/initializers/)。
 embeddings_regularizer: embeddings matrix 的正则化方法
(详见https://keras.io/regularizers/)。
 embeddings_constraint: embeddings matrix 的约束函数 (详见 https://keras.io/constraints/)。
 mask_zero: 是否把 0 看作为一个应该被遮蔽的特殊的 "padding" 值。这对于可变长的循环神经网络层十分有用。 如果设定为 True,那么接下来的所有层都必须支持 masking,否则就会抛出异常。 如果 mask_zero 为 True,作为结果,索引 0 就不能被用于词汇表中 (input_dim 应该与 vocabulary + 1 大小相同)。
 input_length: 输入序列的长度,当它是固定的时。 如果你需要连接 Flatten 和 Dense 层,则这个参数是必须的 (没有它,dense 层的输出尺寸就无法计算)。
 输入尺寸
尺寸为 (batch_size, sequence_length) 的 2D 张量。
 输出尺寸
尺寸为 (batch_size, sequence_length, output_dim) 的 3D 张量。
更多信息可参考官网:
https://keras.io/layers/embeddings/
https://keras.io/zh/layers/embeddings/(中文)
假设定义一个词汇量为200的嵌入层的整数编码单词,将词嵌入到32维的向量空间,每次输入50个单词的输入文档,那么对应的embedding可写成如下格式:

为更好理解Keras的Embedding层的使用,下面列举几个具体实例。
(1)简单实例代码:

(2)用Embedding学习文本表示实例
假设有10个文本文档,每个文档都有一个学生提交的工作评论。每个文本文档被分类为正的“1”或负的“0”。这是一个简单的情感分析问题。用Keras的Embedding学习这10个文本的表示,具体实现代码如下;
#导入需要的模块

运行结果如下:
把文本转换为整数
[[49, 28], [33, 36], [21, 41], [18, 36], [32], [29], [18, 41], [30, 33], [18, 36], [43, 17, 28, 34]]
填充向量
[[49 28 0 0]
[33 36 0 0]
[21 41 0 0]
[18 36 0 0]
[32 0 0 0]
[29 0 0 0]
[18 41 0 0]
[30 33 0 0]
[18 36 0 0]
[43 17 28 34]]
查看模型结构
Model: "sequential_5"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_5 (Embedding) (None, 4, 8) 400
_________________________________________________________________
flatten_4 (Flatten) (None, 32) 0
_________________________________________________________________
dense_4 (Dense) (None, 1) 33
=================================================================
Total params: 433
Trainable params: 433
Non-trainable params: 0
_________________________________________________________________
None
查看模型精度
Accuracy: 89.999998

查看通过50次迭代后的Embedding矩阵。

运行结果如下:
array([[ 0.03469506, 0.05556902, -0.06460979, 0.04944995, -0.04956526,
-0.01446372, -0.01657126, 0.04287368],
[ 0.04969586, -0.0284451 , -0.03200825, -0.00149088, 0.04212971,
-0.00741715, -0.02147427, -0.02345204],
[ 0.00152697, 0.04381416, 0.01856637, -0.00952369, 0.04007444,
0.00964203, -0.0313913 , -0.04820969]], dtype=float32)

3.1.2 Dense简介

Dense就是常用的的全连接层,它实现以下操作:

其中 activation 是按逐个元素计算的激活函数,kernel 是由网络层创建的权值矩阵,以及 bias 是其创建的偏置向量 (只在 use_bias 为 True 时才有用)。
注意: 如果该层的输入的秩大于2,那么它首先被展平然后 再计算与 kernel 的点乘。
【参数说明】
 units: 正整数,输出空间维度。
 activation: 激活函数 (详见 activations)。 若不指定,则不使用激活函数 (即,「线性」激活: a(x) = x)。
 use_bias: 布尔值,该层是否使用偏置向量。
 kernel_initializer: kernel 权值矩阵的初始化器 (详见https://keras.io/zh/initializers/)。
 bias_initializer: 偏置向量的初始化器 (详见https://keras.io/zh/initializers/).
 kernel_regularizer: 运用到 kernel 权值矩阵的正则化函数 (详见 https://keras.io/zh/regularizers/)。
 bias_regularizer: 运用到偏置向的的正则化函数 (详见 https://keras.io/zh/regularizers/)。
 activity_regularizer: 运用到层的输出的正则化函数 (它的 "activation")。 (详见 https://keras.io/zh/regularizers/)。
 kernel_constraint: 运用到 kernel 权值矩阵的约束函数 (详见https://keras.io/zh/constraints/)。
 bias_constraint: 运用到偏置向量的约束函数 (详见https://keras.io/zh/constraints/)。
 输入尺寸
nD张量,尺寸: (batch_size, ..., input_dim)。 最常见的情况是一个尺寸为 (batch_size, input_dim) 的2D输入。
 输出尺寸
nD张量,尺寸: (batch_size, ..., units)。 例如,对于尺寸为 (batch_size, input_dim)的2D输入, 输出的尺寸为 (batch_size, units)。

简单示例代码:

3.2 NN架构


图3-1 NN架构图
从图3-1可知,NN共处理7个特征,其中promo特征因只有2个值(0或1),无需转换为Embedding向量,其它6个特征,根据类别数,分别做了Embedding处理。处理后合并这7个特征,再通过3个全连接层。

3.3 分类特征处理

基于第2章(《XGBoost与NN谁更牛?》)处理保存的feature_train_data.pickle文件,做如下处理:

3.3.1 数据预处理

(1)定义对特征进行Embedding处理函数

(2)导入模块

(3)读取数据

(4)生成训练、测试数据

(5)定义采样函数

(6)采样生成训练数据

3.3.2 构建模型

(1)定义Model类

(2)构建模型

(3)训练模型

运行部分结果
Fitting NN_with_EntityEmbedding...
Train on 200000 samples, validate on 84434 samples
Epoch 1/10
200000/200000 [==============================] - 37s 185us/sample - loss: 0.0140 - val_loss: 0.0113
Epoch 2/10
200000/200000 [==============================] - 33s 165us/sample - loss: 0.0093 - val_loss: 0.0110
Epoch 3/10
200000/200000 [==============================] - 34s 168us/sample - loss: 0.0085 - val_loss: 0.0104
Epoch 4/10
200000/200000 [==============================] - 35s 173us/sample - loss: 0.0079 - val_loss: 0.0107
Epoch 5/10
200000/200000 [==============================] - 37s 184us/sample - loss: 0.0076 - val_loss: 0.0100
Epoch 6/10
200000/200000 [==============================] - 38s 191us/sample - loss: 0.0074 - val_loss: 0.0095
Epoch 7/10
200000/200000 [==============================] - 31s 154us/sample - loss: 0.0072 - val_loss: 0.0097
Epoch 8/10
200000/200000 [==============================] - 33s 167us/sample - loss: 0.0071 - val_loss: 0.0091
Epoch 9/10
200000/200000 [==============================] - 36s 181us/sample - loss: 0.0069 - val_loss: 0.0090
Epoch 10/10
200000/200000 [==============================] - 40s 201us/sample - loss: 0.0068 - val_loss: 0.0089
Result on validation data: 0.09481584162850512
Train on 200000 samples, validate on 84434 samples
Epoch 1/10
200000/200000 [==============================] - 38s 191us/sample - loss: 0.0143 - val_loss: 0.0125
Epoch 2/10
200000/200000 [==============================] - 41s 206us/sample - loss: 0.0096 - val_loss: 0.0107
Epoch 3/10
200000/200000 [==============================] - 46s 232us/sample - loss: 0.0089 - val_loss: 0.0105
Epoch 4/10
200000/200000 [==============================] - 39s 197us/sample - loss: 0.0082 - val_loss: 0.0099
Epoch 5/10
200000/200000 [==============================] - 39s 197us/sample - loss: 0.0077 - val_loss: 0.0095
Epoch 6/10
200000/200000 [==============================] - 41s 207us/sample - loss: 0.0075 - val_loss: 0.0111
Epoch 7/10
200000/200000 [==============================] - 39s 193us/sample - loss: 0.0073 - val_loss: 0.0092
Epoch 8/10
200000/200000 [==============================] - 50s 248us/sample - loss: 0.0071 - val_loss: 0.0092
Epoch 9/10
200000/200000 [==============================] - 46s 228us/sample - loss: 0.0070 - val_loss: 0.0094
Epoch 10/10
200000/200000 [==============================] - 44s 221us/sample - loss: 0.0069 - val_loss: 0.0091
Result on validation data: 0.09585602861091462

3.3.3 验证模型

运行结果如下:
Evaluate combined models...
Training error...
0.06760082089742254
Validation error...
0.09348419043167332

3.4 可视化Entity Embedding

把特征转换为Entity Embedding之后,可以利用t-SNE进行可视化,如对store特征的Embedding降维后进行可视化,从可视化结果揭示出一些重要信息,彼此相似的类别比较接近。

3.4.1 保存Embedding

3.4.2 可视化Embedding特征

(1)导入模块

(2)读取保存的embedding文件

(3)定义对应各州的名称

(4)可视化german_states_embedding

可视化结果如下:


图3-2 可视化german_states_embedding
从图3-2 可知,德国的原属于东德的几个州:萨克森州、萨克森安哈尔特州、图林根州彼此比较接近。其它各州也有类似属性,这就是Embedding通过多次迭代,从数据中学习到一些规则。


XGBoost是Kaggle上的比赛神器,近些年在kaggle或天池比赛上时常能斩获大奖,不过这样的历史正在改变!最近几年神经网络的优势开始从非结构数据向结构数据延伸,而且在一些Kaggle比赛中取得非常不错的成绩。
XGBoost很牛,不过更牛的应该是NN! 这章我们通过一个实例来说明,本实例基于相同数据,使用XGBoost和神经网络(NN)对类别数据转换为数字。XGBoost是属于Tree Model,故是否使用One-hot影响不大(通过测试,确实如此,相反,如果转换为one-hot将大大增加内存开销),所以使用xgboost的数据转换为数字后,没有再转换为one-hot编码。对神经网络(NN)而言,是否把数据转换为One-hot,影响比较大,所以使用NN的模型数据已转换为One-hot。
两种算法测试结果为表2-1。

训练及测试数据没有打乱,其中测试数据是最新原数据的10%。

2.1 XGBoost简介

2.1.1概述

XGBoost的全称是eXtreme Gradient Boosting,由很多CART(Classification And Regression Tree)树集成,其中CART是对分类决策树和回归决策树的总称。
分类决策树一般使用信息增益、信息增益率、基尼系数来选择特征的依据。CART回归树是假设树为二叉树,通过不断将特征进行分裂。比如当前树结点是基于第j个特征值进行分裂的,设该特征值小于s的样本划分为左子树,大于s的样本划分为右子树。因此,当我们为了求解最优的切分特征j和最优的切分点s,就转化为求解这么一个目标函数。

只要遍历所有特征的的所有切分点,就能找到最优的切分特征和切分点。最终得到一棵回归树。

2.1.2 主要原理

XGBoost本质上还是一个GBDT(Gradient Boosting Decision Tree),但为力争把速度和效率发挥到极致,所以叫X (Extreme) GBoosted。GBDT的原理就是所有弱分类器的结果相加等于预测值,然后下一个弱分类器去拟合误差函数对预测值的梯度(或残差)(这个梯度/残差就是预测值与真实值之间的误差)。一个弱分类器如何去拟合误差函数残差?
举一个非常简单的例子,比如我今年30岁了,但计算机或者模型GBDT并不知道我今年多少岁,那GBDT咋办呢?
(1)它会在第一个弱分类器(或第一棵树中)随便用一个年龄比如20岁来拟合,然后发现误差有10岁;
(2)在第二棵树中,用6岁去拟合剩下的损失,发现差距还有4岁;
(3)在第三棵树中用3岁拟合剩下的差距,发现差距只有1岁了;
(4)在第四课树中用1岁拟合剩下的残差,完美。
最终,四棵树的结论加起来,就是真实年龄30岁。实际工程中GBDT是计算负梯度,用负梯度近似残差。
注意,为何GBDT可以用用负梯度近似残差呢?
回归任务下,GBDT 在每一轮的迭代时对每个样本都会有一个预测值,此时的损失函数为均方差损失函数,表达式如下:

那此时的负梯度是这样计算的,具体表达式如下:

所以,当损失函数选用均方损失函数是时,每一次拟合的值就是(真实值 - 当前模型预测的值),即残差。此时的变量是,即“当前预测模型的值”,也就是对它求负梯度。
更多详细内容可参考:https://blog.csdn.net/v_july_v/article/details/81410574

2.1.3 主要优点

(1)目标表达式:
XGBoost优化了GBDT的目标函数。一方面,在GBDT的基础上加入了正则项,包括叶子节点的个数和每个叶子节点输出的L2模的平方和,正则项可以控制树的复杂度,让模型倾向于学习简单的模型,防止过拟合;另外,XGBoost还支持线性分类器,传统的GBDT是以CART算法作为基学习器。
(2)使用Shrinkage:
对每棵树的预测结果采用了shrinkage,相当于学习率,降低模型对单颗树的依赖,提升模型的泛化能力。
(3)采用列采样:
XGBoost借助了随机森林的优点,采用了列采样,进一步防止过拟合,加速训练过程,而传统的GBDT则没有列采样。
(4)优化方法:
XGBoost对损失函数的展开采用了一阶梯度和二阶梯度,而传统的GBDT只采用了一阶梯度。
(5)增益计算:
对分裂依据进行了优化。不同于CART算法,XGBoost采用了新的基于一阶导数和二阶导数的统计信息作为树的结构分数,采用分列前的结构与分裂后的结构得分的增益作为分裂依据,选择增益最大的特征值作为分裂点,替代了回归树的误差平方和。
(6)最佳增益节点查找:
XGBoost在寻找最佳分离点的方式上采用了近似算法,基于权重的分位数划分方式(权重为二阶梯度)。主要是对特征取值进行分桶,然后基于桶的值计算增益。
(7)预排序。
在寻找最佳增益节点时,将所有数据放入内存进行计算,得到预排序结果,然后在计算分裂增益的时候直接调用。
(8)缺失值处理
对于特征的值有缺失的样本,Xgboost可以自动学习出他的分裂方向。Xgboost内置处理缺失值的规则。
(9)支持并行。
众所周知,Boosting算法是顺序处理的,也是说Boosting不是一种串行的结构吗?怎么并行的?注意XGBoost的并行不是tree粒度的并行。XGBoost也是一次迭代完才能进行下一次迭代的(第t次迭代的代价函数里包含)。XGBoost的并行式在特征粒度上的,也就是说每一颗树的构造都依赖于前一颗树。

2.1.4 XGBoost的模型参数

XGBoost使用字典的方式存储参数,主要参数有如下这些:

安装XGBoost建议使用conda命令。如:conda install py-xgboost=0.90

2.2 NN简介

使用的神经网络结构如下图所示

图2-1 神经网络结构
本实例使用的神经网络结构比较简单,共4层,除输入、输出层外,中间是两个全连接层。输入层1183个节点,这个正好是特征转换为one-hot后的元素个数,第1个隐含层的节点是1000,激活函数为relu,第2个隐含层的节点数为500,激活函数为relu,输出层1个节点,激活函数为sigmoid。

2.3 数据集简介

这里使用德国Rossmann超市2013、2014、2015三年的销售数据,具体数据文件包括:
train.csv-包括销售在内的历史数据
test.csv-测试数据(不包括销售)
store.csv-有关商店的补充信息
这些数据可从这里下载:https://www.kaggle.com/c/rossmann-store-sales/data
1、train.csv-包括销售在内的历史数据,共有9列,每列的含义如下:
date(日期):代表存储期
DayOfWeek(星期几):7表示周日,6表示周六以此类推
store(商店):每个商店的唯一ID
sale(销售):特定日期的营业额(这是您的预期)
customer(客户):特定日期的客户数量
open(开):为对存储是否被打开的指示:0 =关闭,1 =开
promo(促销):表示商店当天是否在进行促销
StateHoliday(州假日):通常,除州外,所有商店都在州法定假日关闭。请注意,所有学校在公共假日和周末都关闭。a =公共假期,b =复活节假期,c =圣诞节,0 =无
SchoolHoliday(学校假日):指示(商店,日期)是否受到公立学校关闭的影响
(1)导入数据

(2)查看前5行数据。

(3)查看是否有空值

Store 0
DayOfWeek 0
Sales 0
Customers 0
Open 0
Promo 0
StateHoliday 0
SchoolHoliday 0
dtype: int64
(4)查看各特征的不同值

共有几年的数据: [2015 2014 2013]
共有几种促销方法: [1 0]
2、store.csv数据集简介
StoreType- 区分4种不同的商店模型:a,b,c,d
Assortment分类 -描述分类级别:a =基本,b =额外,c =扩展
CompetitionDistance-距离最近的竞争对手商店的距离(以米为单位)
CompetitionOpenSince [Month / Year] -给出最近的竞争对手开放的大概年份和月份。
Promo促销 -表示商店当天是否在进行促销
Promo2 -Promo2是某些商店的连续和连续促销:0 =商店不参与,1 =商店正在参与
Promo2Since [年/周] -描述商店开始参与Promo2的年份和日历周。
PromoInterval-描述启动Promo2的连续间隔,并指定重新开始促销的月份。例如,“ Feb,May,Aug,Nov”表示该商店的每一轮始于该年任何一年的2月,5月,8月,11月

2.4使用Xgboost算法实例

2.4.1 读取数据

(1)导入模块

(2)定义数据预处理函数

(3)读取数据

2.4.2 预处理数据

(1)导入模块

(2)读取处理后的文件

(3)定义预处理store数据函数

(4)生成训练数据

2.4.3 保存预处理数据

(1)把各类别特征转换为整数,并保存为pickle文件

2.4.4 采样生成训练与测试数据

(1)读取数据

(2)生成训练与测试集

(3)定义采样函数

(4)采样数据

2.4.5 构建模型

(1)导入模块

(2)构建xgboost模型

2.4.6 训练模型

运行结果如下:
[2980] train-rmse:0.148366
[2981] train-rmse:0.148347
[2982] train-rmse:0.148314
[2983] train-rmse:0.148277
[2984] train-rmse:0.148238
[2985] train-rmse:0.148221
[2986] train-rmse:0.148218
[2987] train-rmse:0.148187
[2988] train-rmse:0.148182
[2989] train-rmse:0.148155
[2990] train-rmse:0.148113
[2991] train-rmse:0.148113
[2992] train-rmse:0.148067
[2993] train-rmse:0.148066
[2994] train-rmse:0.148064
[2995] train-rmse:0.148062
[2996] train-rmse:0.148048
[2997] train-rmse:0.148046
[2998] train-rmse:0.148046
[2999] train-rmse:0.148041
Result on validation data: 0.14628885960491078

2.5 使用NN算法实例

2.5.1 预处理数据

导入数据、对数据进行预处理,这些与2.4小节中的2.4.1、2.4.2一样,接下来对个特征先转换为数字,然后转换为one-hot编码,并保存。
(1)把数据转换为one-hot编码

(2)保存数据

2.5.2 生成训练数据

(1)读取数据

(2)生成训练数据

(3)定义采样函数

(4)通过采样生成训练数据

2.5.3 构建神经网络模型

(1)导入模块

(2)定义Model类

(3)构建神经网络

2.5.4 训练模型

运行结果:
Fitting NN...
Train on 200000 samples, validate on 84434 samples
Epoch 1/10
200000/200000 [==============================] - 12s 60us/sample - loss: 0.0121 - val_loss: 0.0142
Epoch 2/10
200000/200000 [==============================] - 11s 54us/sample - loss: 0.0080 - val_loss: 0.0104
Epoch 3/10
200000/200000 [==============================] - 11s 53us/sample - loss: 0.0071 - val_loss: 0.0100
Epoch 4/10
200000/200000 [==============================] - 11s 54us/sample - loss: 0.0064 - val_loss: 0.0099
Epoch 5/10
200000/200000 [==============================] - 11s 54us/sample - loss: 0.0059 - val_loss: 0.0098
Epoch 6/10
200000/200000 [==============================] - 11s 54us/sample - loss: 0.0055 - val_loss: 0.0103
Epoch 7/10
200000/200000 [==============================] - 11s 55us/sample - loss: 0.0051 - val_loss: 0.0100
Epoch 8/10
200000/200000 [==============================] - 11s 54us/sample - loss: 0.0047 - val_loss: 0.0099
Epoch 9/10
200000/200000 [==============================] - 11s 53us/sample - loss: 0.0045 - val_loss: 0.0102
Epoch 10/10
200000/200000 [==============================] - 11s 54us/sample - loss: 0.0042 - val_loss: 0.0100
Result on validation data: 0.10825838109273625

接下来将用更多实例,从多个角度比较NN与传统ML的比较,带EE(Entity Embedding)的NN与不带NN的比较,带EE的传统ML与不带EE的传统ML,以及EE在ML的顶级应用(如推荐算法、预测等问题上的使用)。
EE就像一座桥梁,把结构化数据与NN(或深度学习算法)连接在一起,大大发挥了NN在处理结构化数据方面的潜能(即NN强大的学习能力),深度学习将彻底改变传统机器学习。

1 分类特征传统处理方法

分类(Categorical)特征常被称为离散特征、分类特征,数据类型通常是object类型,而机器学习模型通常只能处理数值数据,所以需要对Categorical数据转换成Numeric特征。
Categorical特征又有两类,我们需要理解它们的具体含义并进行对应的转换。
(1)有序(Ordinal)类型:
这种类型的Categorical存在着自然的顺序结构,如果你对Ordinal 类型数据进行排序的话,可以是增序或者降序,比如在衣服型号这个特征中具体的值可能有:S、M、L、XL等衣服尺码,其中S:(Small)表示小 ,M(Middle)表示中 ,L(Large)表示大,XL(extra large)表示加大尺码,它们之间有XL>L>M>S的大小关系。
(2)常规(Nominal)类型或无序类型:
这种是常规的Categorical类型,不能对Nominal类型数据进行排序,这类特征无谁大谁小之分。比如颜色特征可能的值有:red、yellow、blue、black等,我们不能说red>yellow>blue>black。
对于Ordinal和Nominal类型数据有不同的方法将它们转换成数字。

1.1 处理有序类型

对于Ordinal类型数据可以使用OrdinalEncoder、LabelEncoder进行编码处理,功能相同,都将每一个类别的特征转换成一个新的整数(0到类别数n-1之间)。例如S、M、L、XL等衣服尺码这四个类别进行OrdinalEncoder(或LabelEncoder)处理后会映射成0、1、2、3,这样数据间的自然大小关系也会保留下来。以下数据集data_set共有4列,这4列从左到右分别表示型号、颜色、性别、标签。其中型号为有序类别,其它都是常规类别。这些都是字符,现在利用sklearn中预处理模块preprocessing,把型号转换为数字,具体代码如下:
(1)导入数据

data_set的结果如下
array([['L', 'red', 'Female', 'yes'],
['M', 'red', 'Male', 'no'],
['M', 'yellow', 'Female', 'yes'],
['XL', 'blue', 'Male', 'no']], dtype='<U6')
(2)进行转换

1.2 处理常规类型

1.1节用OrdinalEncoder、OrdinalEncoder把分类变量型号转换为数字,同样可以把颜色、性别、标签等这些属于常规类型的特征转换为整数(0到类别数n-1之间)。具体代码如下:

1.2.1 把标签转换为数字

使用LabelEncoder把标签特征转换为数字。

1.2.2 把颜色、性别转换为数字

运行结果如下:
[[1. 0.]
[1. 1.]
[2. 0.]
[0. 1.]]
在表示颜色这一列中,我们使用[0,1,2]代表了三个不同的颜色,然而这种转换是正确的吗?[0,1,2]这三个数字在算法看来,是连续且可以计算的,这三个数字相互不等,有大小,甚至有着可以相加相乘的联系。所以算法会把颜色,性别这样的常规分类特征,都误会成是有序特征这样的分类特征,把本来互相平等、独立的颜色特征误认为有大小的区分,如blue>yellow>red,blue的重要性是yellow颜色的2倍,这显然是不合理的。因此,我们把分类转换成数字的时候,忽略了数字中自带的数学性质,所以给算法传达了一些不准确的信息,而这会影响我们的建模。如何解决这个问题?
对于Nominal类型数据可以使用OneHotEncoder进行编码处理,尽量向算法传达最准确的信息。

1.2.3 使用OneHotEncoder方法

对于常规类别特征采用独热编码(One-Hot)方式处理,可以保证特征的基本属性,向算法专递最准确的信息。one-hot如何做到这点的呢?首先我们来了解一下one-hot编码的原理。
独热编码会为每个离散值创建一个哑特征(dummy feature)。什么是哑特征呢?举例来说,对于‘颜色’这一特征中的‘blue’,我们将其编码为[blue=1,yellow=0,red=0],同理,对于‘yellow’,我们将其编码为[blue=0,yellow=1,red=0],特点就是向量只有一个1,其余均为0,故称之为one-hot。即把颜色特征转换成如下表示:

图1-1 one-hot编码示意图
从图1-1可知,独热编码进行了如下转换:
①把字符类型转换为数字类型。
②把一个字段(颜色字段)变成3个字段(字段个数为颜色的类别总数)
③对新创建的3个字段,颜色对应位置的值置为1,其它2列位置的都是0。
这么做的目的是为了保证每一个离散取值的“无序性、公平性、彼此正交性”。
下面我们用代码实现颜色和性别字段的one-hot转换。
(1)把字符转换为数字

打印结果如下:
[[1. 0.]
[1. 1.]
[2. 0.]
[0. 1.]]
(2)把数字转换为独热编码

打印结果如下:
[[0. 1. 0. 1. 0.]
[0. 1. 0. 0. 1.]
[0. 0. 1. 1. 0.]
[1. 0. 0. 0. 1.]]
前3列为颜色,后2列为性别。

【说明】OneHotEncoder是sklearn方法,pandas有一个对于方法,即get_dummies,它可直接把字符转换为oneHot编码。具体使用可参考如下博客:
https://blog.csdn.net/maymay_/article/details/80198468

对于Nominal类型数据可以使用独热编码(OneHotEncoder)有其合理的一面,但也有很多不足,如当遇到大数据,一个特征的类别很多几百,甚至几千或更多,而且这样的特征还有很多,如此一来,把这些特征转换成one-hot编码后,特征数将非常巨大!此外,这种方法只是简单把类别数据转换成0或1,无法准确反应这些特征内容隐含的一些规则或这些类别的分布信息,如一个表示地址的特征,可能包括北京、上海、杭州、美国纽约、华盛顿、洛杉矶,东京、大阪等等,这些属性具有一定分布特性,北京、上海、杭州之间的距离较近,而上海与纽约之间的距离应该比较远,诸如此类问题,one-hot是无法表示的。
是否有更有好、更有效的处理方法呢?有,就是接下来将介绍的Learned Embedding方法。

2 使用Embendding方法处理分类特征

2.1传统处理方法的不足

(1)无法真是反应特征的含义
如果仅仅把分类特征转换为数字,可能将无序变成有序,有序的变成可运算(如把M,X,XL转变为0、1、2,那么1+1=2,即X+X=XL,这显然不合逻辑)。
(2)容易导致维度暴增
如果把分类特征转换为one-hot编码,虽然可以使常规分类特征表现更公平、独立,但极易导致维度暴增。如比如阿里上的商品维度就至少是千万量级的,而且这样的商品很多,如果采用one-hot编码,则维度马上变成亿级以上。如果处理语言,词汇量更是几千、几万。除了维度暴增,还导致矩阵的极端稀疏,而太过稀疏数据不利于在机器学习或深度学习中提升性能或增强其泛化能力的。
(3)无法揭示特征内部的规则
很多商品、地址、词汇分类特征,其内容往往包含很多规则,如商品之间的层次关系、地址之间的依赖关系、词汇之间的相似性等,无法通过简单数字化来表达。

2.2 Embedding方法简介

近几年,从计算机视觉到自然语言处理再到时间序列预测,神经网络、深度学习的应用越来越广泛。在深度学习的应用过程中,Embedding 这样一种将离散变量转变为连续向量的方式为传统机器学习、神经网络在各方面的应用带来了极大的扩展。该技术目前主要有两种应用,NLP 中常用的Word Embedding以及用于类别数据的Entity Embedding。
简单来说,Embedding就是用一个低维的向量表示一个事物,可以是一个词、一个类别特征(如商品,电影、物品等)或时间序列特征等。这个Embedding向量通过学习可更准确的表示对应特征的内在含义,使距离相近的向量对应的物体有相近的含义,如图1-2所示

图1-2 可视化销售地址的Embedding
图1-2 是一个有关销售地址的Embedding图形,这是通过神经网络不断学习,得到有关销售地址的Embedding向量,具体代码实现方法后续将介绍。
Embedding往往放在神经网络的第一层,Embedding层可以训练过程不断更新,可以学习到对应特征的内在关系,含Embedding的网络结构可参考图1-3,所以Embedding有时又称为Learned Embedding。一个模型学习到的learned Embedding,也可以被其他模型重用。

图1-3 含Embedding层的神经网络结构图
图1-3把两个分类特征(input_3,input_4)转换为Embedding后,与连续性输入特征(input_5)合并在一起,然后,连接全连接层等。在训练过程中,Embedding向量不断更新。
Embedding的灵感来自Word2Vec,但与Word2Vec有些不同,Word2Vec是google于2013年开源的一个计算词向量的工具。
目前各大深度学习平台都提供了Embedding层:
 PyTorch1+的Embedding层是:
torch.nn.Embedding(m,n),
 TensorFlow2+的Embedding层为:
tf.layers.Embedding(vocab_size, embedding_dim, input_length=maxlen),
 keras的是:
keras.layers.Embedding(input_dim, output_dim, embeddings_initializer='uniform', embeddings_regularizer=None, activity_regularizer=None, embeddings_constraint=None, mask_zero=False, input_length=None)

2.3 Embedding的拓展

近些年Embedding发展很快,可以说很多内容都可表示为Embedding,从最初的word2vec到类别特征可以表示为Embedding(又成为Entity Embedding),时间序列数据可以表示Sequnce Embedding,目前推荐系统经常使用的Item Embedding和User Embedding,和目前的研究热点之一的Graph Embedding等等。可以说万物都可Embedding,目前Embedding已成为深度学习的基本操作。

2.4 Embedding方法的巨大威力

(1)Embedding在各种比赛中取得名列前茅
在结构化数据上运用神经网络时,Entiry Embedding表现的很好。 例如,在Kaggle竞赛”预测出租车的距离问题”上获得第1名的解决方案,就是使用Entiry Embedding来处理每次乘坐的分类元数据(Alexandre de Brébisson,2015)。 同样,预测Rossmann药店销售任务的获得第3名的解决方案使用了比前两个方案更简单的方法: 使用简单的前馈神经网络, 再加上类别变量的Entity Embedding。它包括超过1000个类别的变量,如商店ID(Guo&Berkahn,2016),作者把比赛结果汇总在一篇论文中(Entity Embeddings of Categorical Variables),在论文作者使用Embedding对传统机器学习与神经网络进行纵向和横行的比较,结果如表1-1。

表1-1的结果对原打乱,然后取10%的测试数据。
从表1-1可以看出,如果不使用EE(Entity Embedding),神经网络的性能优于传统机器学习,如果使用EE,效果就更加明显。

表1-2 不打乱原数据,从最新的数据中取10%作为测试数据。
(2)Embedding是现代推荐系统中重要部分
微软的 Deep Crossing、Google 的 Wide&Deep 、YouTube深度学习推荐系统、阿里 DIN(2018 年)、华为 DeepFM系统、美团的推荐系统等等都把Embedding作为其重要组件。图1-4 为Google Wide&Deep(2016 年)的架构图。

图1-4 Google Wide&Deep架构图

3、小试牛刀:使用Embedding处理类别特征

本实例使用breast-cancer1数据集(下载),共有10列,其中前9列为分类特征,最后1列为标签,共有285行数据。

主要步骤如下:
(1)导入需要的模块
(2)定义导入数据的函数
首先,数据中含?的项替换为nan值,然后删除含nan的行。然后把前9列放入X,最后1列放入y
(3)先把类别特征数字化,然后在转换为长度都为10的Embedding向量
(4)合并这些Embedding向量
(5)构建模型,模型结构如1-5所示。
模型结构图1-5所示

图1-5
(6)训练及评估模型
通过一般分类特征处理方法及使用传统机器学习该数据集能获得74%左右的精度,这里得到77%左右的精度。

运行结果如下:
Epoch 15/20
184/184 - 0s - loss: 0.4477 - accuracy: 0.7989
Epoch 16/20
184/184 - 0s - loss: 0.4399 - accuracy: 0.8152
Epoch 17/20
184/184 - 0s - loss: 0.4351 - accuracy: 0.8152
Epoch 18/20
184/184 - 0s - loss: 0.4305 - accuracy: 0.8043
Epoch 19/20
184/184 - 0s - loss: 0.4250 - accuracy: 0.8043
Epoch 20/20
184/184 - 0s - loss: 0.4212 - accuracy: 0.8043
Accuracy: 77.17


欢迎加入本书的在线答疑及交流群!加入QQ:799038260 或扫以下二维码

本书代码及数据下载

提取码:lyl0

目 录

第一部分 Embedding基础部分

第1章 万物皆可Embedding

第2章 获取Embedding的几种方法

第3 章 计算机视觉处理

第4章 文本及序列处理

第5章 注意力机制

第6章 从Word Embedding到ELMO

第7章 从ELMo到BERT和GPT

1.GPT可视化
2.GPT-3简介
3.ChatGPT简介

第8章 BERT几种典型改进方法

第9章 推荐系统

第二部分 Embedding应用实例

第10 章 用Embedding表现分类特征

第11 章 用Embedding提升机器学习性能

第12 章 用Transformer实现英文翻译中文

第13 章 Embedding在推荐系统中的应用

第14章 用BERT实现中文语句分类

14.2 可视化BERT注意力权重

第15章 用GPT2生成文本

第16章 Embedding技术总结


我这次TensorFlow的升级之路,可以用一句话来概括:“山重水复疑无路,柳暗花明又一村”

1.1环境分析

1、目标:升级到TensorFlow-GPU 2.0
2、原有环境:
Python3.6,TensorFlow-GPU 1.6,ubuntu16.04,GPU驱动为NVIDIA-SMI 387.26
3、“硬核”:
①如果要升级到TensorFlow-gpu 2.0,cuda 应该是10.+,而10.+,根据下表1-1可知,GPU的Driver Version应该>=410+,但我目前的Driver Version 387.26。
②TensorFlow支持Python3.7
4、在安装TensorFlow-gpu 2.0之前需要做的事情
①升级GPU Driver Version>=410(最关键)
②安装Python3.7
③安装cuda 10
④安装TensorFlow-gpu 2.0

1.2参考资料

以下这些参考资料在安装过程中可能需要。
1、如何查找GPU型号与Driver version之间的关系?
安装新的支持cuda10+的驱动,具体安装驱动程序,可登录:
https://www.nvidia.com/Download/Find.aspx?lang=en-us 得到图1-1界面,输入对于GPU型号获取对于驱动程序。

图1-1 GPU型号及产品系列兼容的Driver版本
2、安装GPU驱动有哪些常用方法?
安装GPU驱动有一些3种方法,前2种操作比较简单,第3种NVIDIA推荐的手动安装方法,定制比较高,但比较繁琐。
①使用标准Ubuntu仓库进行自动化安装
②使用PPA仓库进行自动化安装
③使用官方的NVIDIA驱动进行手动安装
3、如何查看当前内核?
安装过程中,可能会出现/boot目录空间问题,这些需要通过一些方法保证/boot空间,方法有删除一些非当前使用的内核或扩充/boot空间等方法。
①查看内核列表

②查看当前使用的内核

③删除内核方法

1.3 安装的准备工作

1、查看显卡基本信息
通过命令nvidia-smi 查看显卡基本信息:
NVIDIA-SMI 387.26 Driver Version: 387.26
2、nvidia 驱动和cuda runtime 版本对应关系
查看nvidia官网:
https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html
表1-1 CUDA与其兼容的Driver版本

从上表可知,因我目前的GPU驱动版本为:Driver Version: 387.26,无法安装cudnn10+
需要升级GPU驱动。

1.4 升级GPU驱动

Ubuntu 社区建立了一个命名为 Graphics Drivers PPA 的全新 PPA,专门为 Ubuntu 用户提供最新版本的各种驱动程序,如Nvidia 驱动。因此我采用通过 PPA 为 Ubuntu 安装 Nvidia 驱动程序,即使用PPA仓库进行自动化安装。
1、卸载系统里的Nvidia低版本显卡驱动

2、把显卡驱动加入PPA

3、更新apt-get

4、查找显卡驱动最新的版本号

返回如下信息

5、采用apt-get命令在终端安装GPU驱动

6、重启系统并验证
(1)重启系统

(2)查看安装情况
在终端输入以下命令行

如果没有输出,则安装失败。成功安装会有如下类似信息。

(3)查看Ubuntu自带的nouveau驱动是否运行

如果终端没有内容输出,则显卡驱动的安装成功!
(4)使用nvidia-smi查看GPU驱动是否正常

至此,GPU驱动已成功安装,驱动版本为418,接下来就可安装TensorFlow、Pytorch等最新版本了!

1.5安装Python3.7

1、安装python3.7
因TensorFlow-GPU 2.0支持python3.7,故需删除python3.6,安装python3.7
(1)使用rm -rf 命令删除目录:anaconda3

(2)到anaconda官网下载最新的最新的anaconda3版本
登录:https://www.anaconda.com/distribution/
得到如下界面:

图1-2 下载anaconda界面
下载很快,506MB,5分钟左右就下载完成。
得到sh程序包:Anaconda3-2019.10-Linux-x86_64.sh
(3)安装python3.7
在命令行执行如下命令:

安装过程中,会有几个问题,一般回答yes即可:
第一个问题:
Do you accept the license terms? [yes|no]
选择yes
第二个问题:
Anaconda3 will now be installed into this location:
~/anaconda3

- Press ENTER to confirm the location
- Press CTRL-C to abort the installation
- Or specify a different location below
按回车ENTER即可
第三个问题:
Do you wish the installer to initialize Anaconda3
by running conda init? [yes|no]
选择yes将把Python安装目录,自动写入.bashrc文件
(4)使用命令conda list查看已安装的一些版本

1.6安装TensorFlow-GPU 2.0

如果使用conda安装TensorFlow-gpu 2.0,可用一个命令搞定,如果用pip安装需要3步。

1.6.1 用conda安装

使用conda安装tensorflow-gpu时,它会自动下载依赖项,比如最重要的cuda和cudnn等,其中cuda将自动安装10版本。
①先查看能安装的TensorFlow包

②安装TensorFlow-GPU 2.0

1.6.2用pip安装

①先安装cudatoolkit

②安装cudnn

③安装TensorFlow-gpu 2.0

【说明】
①如果使用conda环境(如果只有一个Python版本,也可不使用conda环境),创建环境时,采用conda create -n tf2 python=3.7,而不是之前版本的source create *。激活环境也是用conda activate tf2 。
②如果卸载需先卸载cudnn,然后再卸载cudatoolkit

1.7 Jupyter notebook的配置

可参考《Python深度学习基于TensorFlow》的8.3小节。

1.8 安装验证

1、验证tensorflow安装是否成功

说明tensorflow-gpu安装成功,而且gpu使用正常。

1.9 TensorFlow一般方式处理实例

1.9、1.10小节,都基于以MNIST数据集,数据预处理相同,模型也相同。1.9节采用keras的一般模型训练方法,1.10节采用分布式处理方法,比较两种方式的处理逻辑、性能及所用时间等指标。

1.9.1导入需要的库

1.9.2导入数据

1.9.3数据预处理

(1)转换为4维数组

(2)获取通道信息

(3)对数据进行缩放

(4)把标签数据转换为二值数据格式或one-hot格式

1.9.4构建模型

模型结构如下。

1.9.5编译模型

1.9.6训练模型

运行结果如下。
Epoch 9/12
60000/60000 [==============================] - 5s 81us/sample - loss: 0.0133 - accuracy: 0.9958 - val_loss: 0.0259 - val_accuracy: 0.9915
Epoch 10/12
60000/60000 [==============================] - 5s 79us/sample - loss: 0.0101 - accuracy: 0.9969 - val_loss: 0.0264 - val_accuracy: 0.9916
Epoch 11/12
60000/60000 [==============================] - 5s 81us/sample - loss: 0.0083 - accuracy: 0.9973 - val_loss: 0.0338 - val_accuracy: 0.9892
Epoch 12/12
60000/60000 [==============================] - 5s 80us/sample - loss: 0.0082 - accuracy: 0.9973 - val_loss: 0.0308 - val_accuracy: 0.9910

1.9.7 GPU的使用情况

一般情况下,实际上只有一个GPU在使用,另一个几乎没有运行。

1.10 TensorFlow分布式处理实例

1.10.1概述

TensorFlow 2.0 开始支持更优的多 GPU 与分布式训练。Tensorflow的分布策略目前主要有四个Strategy:
 MirroredStrategy
 CentralStorageStrategy
 MultiWorkerMirroredStrategy
 ParameterServerStrategy
这里主要介绍第1种策略,即镜像策略(MirroredStrategy)。TensorFlow 2.0 在多 GPU 训练上是否更好了呢?是的,镜像策略用于单机多卡数据并行同步更新的情况,在每个GPU上保存一份模型副本,模型中的每个变量都镜像在所有副本中。这些变量一起形成一个名为MirroredVariable的概念变量。通过apply相同的更新,这些变量保持彼此同步。
镜像策略用了高效的All-reduce算法来实现设备之间变量的传递更新。默认情况下,它使用NVIDIA NCCL作为all-reduce实现。用户还可以在官方提供的其他几个选项之间进行选择。如图1-3所示。

图1-3 TensorFlow使用多GPU示意图
(1)假设你的机器上有2个GPU。
(2)在单机单GPU的训练中,数据是一个batch一个batch的训练。 在单机多GPU中,数据一次处理2个batch(假设是2个GPU训练), 每个GPU处理一个batch的数据计算。
(3)变量,或者说参数,保存在CPU上。
(4)刚开始的时候数据由CPU分发给2个GPU, 在GPU上完成了计算,得到每个batch要更新的梯度。
(5)然后在CPU上收集完了2个GPU上的要更新的梯度, 计算一下平均梯度,然后更新参数。
(6)然后继续循环这个过程。

1.10.2创建一个分发变量和图形的镜像策略

1.10.3定义批处理等变量

1.10.4创建数据集并进行分发

1.10.5创建模型

1.10.6创建存储检查点

1.10.7定义损失函数

1.10.8定义性能衡量指标

如损失和准确性

1.10.9训练模型

(1)定义优化器、计算损失值

(2)训练模型

运行结果如下。
Epoch 9, Loss: 1.0668369213817641e-05, Accuracy: 99.91753387451172, Test Loss: 0.041710007935762405, Test Accuracy: 99.09666442871094
Epoch 10, Loss: 0.006528814323246479, Accuracy: 99.90166473388672, Test Loss: 0.04140192270278931, Test Accuracy: 99.10091400146484
Epoch 11, Loss: 0.001252010464668274, Accuracy: 99.90159606933594, Test Loss: 0.04158545285463333, Test Accuracy: 99.10043334960938
Epoch 12, Loss: 0.0014430719893425703, Accuracy: 99.90159606933594, Test Loss: 0.041613057255744934, Test Accuracy: 99.09874725341797

1.10.10 GPU使用情况

由此可知,采用分布式方式,两个GPU都得到充分使用。

1.11 建议使用conda安装TensorFlow

https://zhuanlan.zhihu.com/p/46599887
使用 TensorFlow 开展机器学习工作的朋友,应该有不少是通过 pip 下载的 TensorFlow。但是近日机器学习专家 Michael Nguyen 大声疾呼:“为了性能起见,别再用 pip 下载 TensorFlow了!”,他强力建议的理由基于以下两点:
1、更快的CPU性能Conda TensorFlow 包利用了用于深度神经网络或 1.9.0 版本以上的 MKL-DNN 网络的英特尔 Math Kernel Library(MKL),这个库能让性能大幅提升。如下图所示:

可以看到,相比 pip 安装,使用 Conda 安装后的性能足足提升了 8 倍。这对于仍然经常使用 CPU 训练的人来说,无疑帮助很大。我(Michael Nguyen——译者注)自己平时在把代码放到 GPU 驱动的机器之前,会先使用 CPU 机器跑一遍,使用 Conda 安装 TensorFlow 能大幅加快迭代速度。
MKL 库不仅能加快 TensorFlow 包的运行速度,也能提升其它一些广泛使用的程序库的速度,比如 Numpy、NumpyExr、Scikit-Learn。
2、简化 GPU 版的安装
Conda 安装会自动安装 CUDA 和 GPU 支持所需的 CuDNN 库,但 pip 安装需要你手动完成。大家都比较喜欢一步到位的吧,特别是下载很多个库的时候。
【说明】有些软件或版本使用conda安装可能找不到,这时需要使用pip安装,使用pip可以安装一些较新版本。

1.12 安装PyTorch

1、登录PyTorch官网
https://pytorch.org/
2、选择安装配置

3、用conda安装
复制执行语句到命令行,进行执行,如安装cpu版的PyTorch

如果这种方式无法执行,或下载很慢,可把-c pytorch去掉, -c参数指明了下载pytorch的通道,优先级比清华镜像更高
使用指定的源(如清华源)可以采用如下命令,这样安装速度应该快很多。
【说明】如果在windows下安装pytorch出现对xx路径没有权重问题时,在进入cmd时,右键选择用管理员身份安装,如图所示:

安装gpu conda 1010版本

4、使用pip安装

5、验证安装是否成功

1.13 修改安装源

我们用pip或conda安装软件时,很慢甚至时常报连接失败等问题,出现这些情况,一般是下载的源是国外的网站。可以修改安装源来大大加速下载速度及其稳定性,以下介绍几种利用清华源的方法。
1、修改conda安装源
在用户当前目录下,创建.condarc文件,然后把以下内容放入该文件即可。

【说明】windows环境也是如此,如果没有.condarc文件,就创建。
2、修改pip安装源
为了和conda保持一致,选择还是清华的镜像源。步骤如下:
(1)修改 ~/.pip/pip.conf 文件。

【说明】如果是windows环境,在用户当前目录下,修改pip\pip.ini文件
没有就创建。
(2)添加源

6.4前馈神经网络

前馈神经网络(Feedforward Neural Network)是最早被提出的神经网络,我们熟知的单层感知机、多层感知机、卷积深度网络等都属于前馈神经网络,它之所以称为前馈(Feedforward),或许与其信息往前流有关:数据从输入开始,流过中间计算过程,最后达到输出层。模型的输出和模型本身没有反馈(Feedback)连接。有反馈连接的称为反馈神经网络,如循环神经网络(Recurrent neural network,简称为RNN),RNN将在第15章介绍。本书如果没有特别说明,神经网络一般指前馈神经网络。
前面我们介绍了机器学习中几种常用算法,如线性模型、SVM、集成学习等有监督学习,这些算法我们都可以用神经网络来实现,如图6-22所示,神经网络的万能近似定理(universal approximation theorem)为重要理论依据。如果用神经网络来实现,还有很多便利,可自动获取特征、自动(或半自动)选择模型函数等。神经网络可以解决传统机器学习问题,更可以解决传统机器学习无法解决或难以解决的问题。因此,近些年神经网络发展非常快、应用也非常广。


图6-22线性模型的神经元
神经网络是深度学习的重要基础,深度学习中的深一般指神经网络的层次较深。接下来我们从最简单也最基础的神经元开始,然后介绍单层感知机、单层感知机的局限性、多层感知机、构建一个多层神经网络、前向传播及反向传播算法及实例等内容。

6.4.1神经元结构

1943年,心理学家McCulloch和数学家Pitts参考了生物神经元的结构(请看图6-23),发表了抽象的神经元模型MP。一个神经元模型包含输入、计算、输出等功能。图6-23是一个典型的神经元模型:包含有3个输入、1个输出、计算功能(先求和,然后把求和结果传递给激活函数f)。


其间的箭头线称为“连接”。每个连接上有一个“权值”,如上图〖的w〗_i,权重是最重要的东西。一个神经网络的训练算法就是让权重的值调整到最佳,以使得整个网络的预测效果最好。
我们使用x来表示输入,用w来表示权值。一个表示连接的有向箭头可以这样理解:在初端,传递的信号大小仍然是x,中间有加权参数w,经过这个加权后的信号会变成x*w,因此在连接的末端,信号的大小就变成了x*w。
输出y是在输入和权值的线性加权及叠加了一个函数f后的值。在MP模型里,函数f又称为激活函数,激活函数将数据压缩到一定范围区间内,其值大小将决定该神经元是否处于活跃状态。
从机器学习的角度,我们习惯把输入称为特征,输出y=f(z)为目标函数。
在6.1.1.小节介绍的逻辑回归实际上就是一个神经元结构,输入为x,参数有w(权重),b(偏移量),求和得到:z=wx+b ,式(6.5)中函数f就是激活函数,该激活函数为阶跃函数。

6.4.2感知机的局限

在讲感知机的局限之前,我们先简单介绍一下感知机。在感知机中,“输入”也作为神经元节点,标为“输入单元”。感知机仅有两个层:分别是输入层和输出层。输入层里的“输入单元”只负责传输数据,不做计算。输出层里的“输出单元”则需要对前面一层的输入进行计算。
图6-24就是一个简单的感知机模型,输入层有3个输入单元,输出层有2个神经元。

图6-24感知机模型
如果我们把输入的变量x_1、x_2、x_3用向量X来表示,即X=[x_1,x_2,x_3 ]^T。权重构成一个2*3矩阵 W(第一行下标为1开头的权重,第2行下标为2开头的权重),输出表示为Y=[y_1,y_2 ]^T。这样输出公式可以改写成:
Y= f(W * X)(6.29)
其中f是激活函数。对激活函数,一般要求:
1)非线性:为提高模型的学习能力,如果是线性,那么再多层都相当于只有两层效果;
2)可微性:有时可以弱化,在一些点存在偏导即可;
3)单调性:保证模型简单。
与神经元模型不同,感知器中的权值是通过训练得到的。如何训练?这主要通过前向传播和反向传播,具体操作后续将详细介绍。感知器对线性可分或近似线性可分数据有很好效果,对线性不可分数据效果不理想。Minsky在1969年出版了叫《Perceptron》的一本书,里面用详细的数学证明了感知器无法解决XOR(异或)分类问题。这应该是比较简单的分类问题,用传统的机器学习算法如SVM等能很好解决,用感知器却无法解决。当时很多人做过多种尝试,如增加输出层的神经个数、调整激活函数等,但结果都不尽人意。后来有人通过增加层数,问题就迎刃而解。接下来我们介绍多层神经网络。

6.4.3多层神经网络

解决XOR问题也不是一帆风顺,增加层实际上很多人都想到了。但增加层以后,计算量、计算的复杂度就上来了,还有如何缩小误差,如何求最优解等问题也自然而然地出现。传统机器学习中我们可以通过梯度下降或最小二乘法等算法解决,但对神经网络如何优化,困扰大家很长时间。
直到1986年,Hinton和Rumelhar等人提出了反向传播(Backpropagation,简称BP)算法,解决了两层神经网络所需要的复杂计算量问题,从而带动了业界使用两层神经网络研究的热潮。目前,大量介绍神经网络的教材,一般重点介绍两层(带一个隐藏层)或多层神经网络的内容。
多层神经网络(含一层或多层隐含层)结构与神经元有何区别呢?图6-25就是一个简单的多层神经网络,除有一个输入层,一个输出层外,中间还有一层,这中间层因看不见其输入或输出数据,所以称为隐含层。

图6-25多层神经网络示意图
现在,我们的权值矩阵增加到了两个,我们用上标来区分不同层次之间的变量。
这里我们使用向量和矩阵来表示层次中的变量,如输入层表示为X^((1)),隐含层为H^((1)),O^((1))是为输出层。W^((1))和W^((2))是网络的矩阵参数。用矩阵来描述,可得到如下计算公式:
f(W^((1))*X^((1)))=H^((1))(6.29)
f(W^((2))*H^((1)))=O^((1))(6.30)
接下来,我们看多层神经网络如何处理XOR问题。
1)首先我们来看,何为XOR问题。

表6-2 异或问题

表6-2 可视化结果为图6-26,其中圆点表示0,方块表示1。AND、OR、NAND问题都是线性可分,但XOR是线性不可分。

图6-26异或问题示意图
2)构建多层网络
(1)确定网络结构

图6-27解决XOR问题的网络结构,左边为详细结构,右边为向量式结构,这种结构比较简洁,而且更贴近矩阵运算模型。
(2)确定输入
由表6-2可知,输入数据中有两个特征:x_1,x_2,共有4个样本。分别为〖[0,0]〗^T,〖[0,1]〗^T,〖 [1,0]〗^T,〖[1,1]〗^T,详细内容见表6-2。取x_1,x_2两列,每行代表一个样本,每个样本x都 是一个向量,如果用矩阵表示就是:

(3)确定隐含层及初始化权重矩阵W、w
隐含层主要确定激活函数,这里我们采用整流线性激活函数: g(z)=max{0,z},如图6-28所示。

图6-28整流线性激活函数(ReLU)图形
初始化以下矩阵:

(5)计算

这样神经网络就得到我们希望的结果。
如何用程序如何实现XOR呢?下节将具体说明。

6.4.4实例:用TensorFlow实现XOR

上节通过一个实例具体说明了如何用多层神经网络来解决XOR问题,不过在计算过程中我们做了很多人工设置,如对权重及偏移量的设置。如果用程序来实现,一般不会这样,如果维度较多,手工设置参数也是不现实的。接下来我们介绍如何用TensorFlow来实现XOR问题。用程序实现,总的思路是:用反向传播算法(BP),循环迭代,直到满足终止条件为止。如果你对BP算法不熟悉,下节将详细介绍。具体步骤如下:
(1)明确输入数据、目标数据

(2)确定网络架构
网络架构采用图6-27
(3)确定几个函数
激活函数、代价函数、优化算法等。这里激活函数还是使用ReLU函数,代价函数使用MSE,优化器使用Adam自适应算法。
(4)初始化权重和偏移量等参数
初始化权重和偏移量,只要随机取些较小值即可,无须考虑一些特殊值,最终这些权重值或偏移量,在循环迭代中不断更新。随机生成权重初始化数据,生成的数据符合正态分布。
(5)循环迭代
循环迭代过程中参数会自动更新
(6)最后打印输出
以下我们用TensorFlow来实现XOR问题的详细代码,为尽量避免循环,这里采用矩阵思维,这样可以大大提升性能,尤其在深度学习环境中。如果你对TensorFlow还不是很熟悉,可以先跳过,或先看一下第9章。

打印结果
step: 0, loss: 0.212
step: 200, loss: 0.000
step: 400, loss: 0.000
step: 600, loss: 0.000
step: 800, loss: 0.000
step: 1000, loss: 0.000
step: 1200, loss: 0.000
step: 1400, loss: 0.000
step: 1600, loss: 0.000
step: 1800, loss: 0.000
X: array([[0, 0],
[0, 1],
[1, 0],
[1, 1]])
pred: array([[ -7.44201316e-07],
[ 9.99997079e-01],
[ 9.99997139e-01],
[ -7.44201316e-07]], dtype=float32)

6.4.5反向传播算法

上节我们用TensorFlow实现了XOR问题,效果不错。整个训练过程就是通过循环迭代,逐步使代价函数值越来越小。如何使代价函数值越来越小?主要采用BP算法。BP算法是目前训练神经网络最常用且最有效的算法,也是整个神经网络的核心之一,它由前向和后向两个操作构成,其主要思想是:
(1)利用输入数据及当前权重,从输入层经过隐藏层,最后达到输出层,求出预测结果, 并利用预测结果与真实值构成代价函数,这是前向传播过程;
(2)利用代价函数,将误差从输出层向隐藏层反向传播,直至传播到输入层,利用梯度下 降法,求解参数梯度并优化参数;
(3)在反向传播的过程中,根据误差调整各种参数的值;不断迭代上述过程,直至收敛。
这样说,或许你觉得还不够具体、不好理解,没关系。BP算法确实有点复杂、不易理解,接下来,我们将以单个神经元如何实现BP算法为易撕口,由点扩展到面、由特殊推广到一般的神经网络,这样或许能大大降低学习BP算法的坡度。

1.单个神经元的BP算法

以下推导要用到微积分中链式法则,这里先简单介绍一下。链式法则用于计算复合函数,而BP算法需要利用链式法则。设x是实数,假设y=g(x),z=f(y)=f(g(x)),
根据链式法则,可得:

如果用向量表示,式(6.32)可简化为:

我们以单个神经元为例,以下是详细步骤。
(1)定义神经元结构
首先我们看只有一个神经元的BP过程。假设这个神经元有三个输入,激活函数为sigmoid函数:
f(x)=1/(1+e^(-x) ) (6.34)
其结构如图6-29所示

图6-29单个神经元,把左图中的神经元展开就得到右图。
(2)进行前向传播,从输入数据及权重开始,往输出层传递,最后求出预测值a,并与目标值y构成代价函数J。
假设一个训练样本为(x,y),其中x是输入向量,x=[x_1,x_2,x_3 ]^T,y是目标值。先把输入数据与权重w=[w_1,w_2,w_3]乘积和求得z,然后通过一个激励函数sigmoid,得到输出a,最后a与y构成代价函数J。具体前向传播过程如下:

(3)进行反向传播,以代价函数开始,从输出到输入,求各节点的偏导。这里分成两步, 先求J对中间变量的偏导,然后求关于权重w及偏移量b的偏导。先求J关于中间变 量a和z的偏导:


在这个过程中,先求∂J/∂a,进一步求∂J/∂z,最后求得∂J/∂w和∂J/∂b,然后利用梯度下降优化算法,变更参数权重参数w及偏移量b,结合上图(6.27)及链导法则,可以看出这是一个将代价函数的增量∂J自后向前传播的过程,因此称为反向传播。
(4)重复第(2)看代价函数J的误差是否满足要求或是否到指定迭代步数,如果不满足条件,继续(3),如此循环,直到满足条件为止。
这节介绍了单个神经元的BP算法。虽然只有一个神经元,但包括了BP的主要内容,正所谓“麻雀虽小,五脏俱全”。不过与一般神经网络还是有点区别,下节我们介绍一个三层神经网络的BP算法。

2.多层神经网络的BP算法

不失一般性,这里我们介绍一个含隐含层,两个输入,两个输出的神经网络,如图6-30。


为简便起见,这里省略偏差b,这个实际可以看做X_n*w_n (X_n=1,w_n=b)。激活函数为f(z)=1/(1+e^(-z) ), 共有三层:第一层为输入层,第二层为隐含层,第三层为输出层,详解结构如图6-30。

输入层(x) 隐含层(h) 输出层(o)
图6-31多层神经网络
整个过程与单个神经元的基本相同,包括前向传播和反向传播两步,只是在求偏导时有些区别。
1)前向传播
把当前的权重参数和输入数据,从下到上(即从输入层到输出层),求取预测结果,并利用预测结果与真实值求解代价函数的值。如图6-32:

图6-32前向传播示意图

具体步骤如下:
(1) 从输入层到隐含层

(2)从隐含层到输出层

(3)计算总误差

2)反向传播
反向传播是利用前向传播求解的代价函数,从上到下(即从输出层到输入层),求解网络的参数梯度或新的参数值,经过前向和反向两个操作后,完成了一次迭代过程,如图6-33。

 

图6-33反向传播示意图
具体步骤如下:
(1)计算总误差

(2)由输出层到隐含层,假设我们需要分析权重参数w_5对整个误差的影响,可以用整体误差对w_5求偏导求出:这里利用微分中的链式法则,涉及过程包括:f(z_O1)--->z_O1----->w_5

图6-34反向传播有输出层到权重w_5


根据式6.42--6.52,不难求出其他权重的偏导数。

(3)由隐含层到输入层,假设我们需要分析权重参数w_1对整个误差的影响,可以用整体误差对w_1求偏导,过程包括:f(z_h1)--->z_h1----->w_1,不过而f(z_h1)会接受E_o1和E_o2两个地方传来的误差,所以这个地方两个都要计算。

输入层(x) 隐含层(h)输出层(o)
图6-35反向传播由隐含层再到权重w_1
代价函数偏导由隐含层到权重w_1,计算如下:

至此,可求得权重w_1的偏导数,按类似方法,可得到其他权重的偏导。权重和偏移量的偏导求出后,再根据梯度优化算法,更新各权重和偏移量。
3)判断是否满足终止条件
根据更新后的权重、偏移量,进行前向传播,计算输出值及代价函数,根据误差要求或迭代次数,看是否满足终止条件,满足则终止,否则,继续循环以上步骤。
现在很多架构都提供了自动微分功能,在具体训练模型时,只需要选择优化器及代价函数,其他无须过多操心。但是,理解反向传播算法的原理对学习深度学习的调优、架构设计等还是非常有帮助的。

6.5 实例:用keras构建深度学习架构

最后给出一个用Keras实现的样例,这里用Keras主要考虑其简洁性,Keras的介绍可参考16.5节。如果你还想进一步了解Keras,可参考Keras中文网站http://keras-cn.readthedocs.io/en/latest/或飞谷云网站:http://www.feiguyunai.com/


深度学习不仅在于其强大的学习能力,更在于它的创新能力。我们通过构建判别模型来提升模型的学习能力,通过构建生成模型来发挥其创新能力。判别模型通常利用训练样本训练模型,然后利用该模型,对新样本x,进行判别或预测。而生成模型正好反过来,根据一些规则y,来生成新样本x。
生成式模型很多,本章主要介绍常用的两种:变分自动编码器(VAE)和生成式对抗网络(GAN)及其变种。虽然两者都是生成模型,并且通过各自的生成能力展现其强大的创新能力,但他们在具体实现上有所不同。GAN基于博弈论,目的是找到达到纳什均衡的判别器网络和生成器网络。而VAE基本根植贝叶斯推理,其目标是潜在地建模,从模型中采样新的数据。
本章主要介绍多种生成式网络,具体内容如下:
用变分自编码器生成图像
GAN简介
如何用GAN生成图像
比较VAE与GAN的异同
CGAN、DCGAN简介

8.1 用变分自编码器生成图像

变分自编码器是自编码器的改进版本,自编码器是一种无监督学习,但它无法产生新的内容,变分自编码器对其潜在空间进行拓展,使其满足正态分布,情况就大不一样了。

8.1.1 自编码器

自编码器是通过对输入X进行编码后得到一个低维的向量z,然后根据这个向量还原出输入X。通过对比X与X ̃的误差,再利用神经网络去训练使得误差逐渐减小,从而达到非监督学习的目的。图8-1 为自编码器的架构图:

图8-1 自编码器架构图
自编码器因不能随意产生合理的潜在变量,从而导致它无法产生新的内容。因为潜在变量Z都是编码器从原始图片中产生的。为解决这一问题,人们对潜在空间Z(潜在变量对应的空间)增加一些约束,使Z满足正态分布,由此就出现了VAE模型,VAE对编码器添加约束,就是强迫它产生服从单位正态分布的潜在变量。正是这种约束,把VAE和自编码器区分开来。

8.1.2变分自编码器

变分自编码器关键一点就是增加一个对潜在空间Z的正态分布约束,如何确定这个正态分布就成主要目标,我们知道要确定正态分布,只要确定其两个参数均值u和标准差σ。那么如何确定u、σ?用一般的方法或估计比较麻烦效果也不好,人们发现用神经网络去拟合,简单效果也不错。图8-2 为AVE的架构图。

图8-2 AVE架构图
在图8-2中,模块①的功能把输入样本X通过编码器输出两个m维向量(mu、log_var),这两个向量是潜在空间(假设满足正态分布)的两个参数(相当于均值和方差)。那么如何从这个潜在空间采用一个点Z?
这里假设潜在正态分布能生成输入图像,从标准正态分布N(0,I)中采样一个ε(模块②的功能),然后使
Z=mu+ exp⁡(log_var)*ε (8.1)
这也是模块③的主要功能。
Z是从潜在空间抽取的一个向量,Z通过解码器生成一个样本X ̃,这是模块④的功能。
这里ε是随机采样的,这就可保证潜在空间的连续性、良好的结构性。而这些特性使得潜在空间的每个方向都表示数据中有意义的变化方向。
以上这些步骤构成整个网络的前向传播过程,反向传播如何进行?要确定反向传播就涉及到损失函数了,损失函数是衡量模型优劣的主要指标。这里我们需要从以下两个方面进行衡量。
(1)生成的新图像与原图像的相似度;
(2)隐含空间的分布与正态分布的相似度。
度量图像的相似度一般采用交叉熵(如nn.BCELoss),度量两个分布的相似度一般采用KL散度(Kullback-Leibler divergence)。这两个度量的和构成了整个模型的损失函数。
以下是损失函数的具体代码,AVE损失函数的推导过程,有兴趣的读者可参考原论文:https://arxiv.org/pdf/1606.05908.pdf

8.1.3用变分自编码器生成图像

前面我们介绍了AVE的架构和原理,至此对AVE的“蓝图”就有了大致了解,如何实现这个蓝图?这节我们将结合代码,用Pytorch实现AVE。此外,还包括在实现过程中需要注意的一些问题,为便于说明起见,数据集采用MNIST,整个网络结构如图8-3所示。

图8-3 AVE网络结构图
首先,我们简单介绍一下实现的具体步骤,然后,结合代码详细说明,如何用Pytorch一步步实现AVE。具体步骤如下:
(1)导入必要的包

(2)定义一些超参数

(3)对数据集进行预处理,如转换为Tensor,把数据集转换为循环、可批量加载的数据集;

(4)构建AVE模型,主要由encode和decode两部分组成;

(5)选择GPU及优化器

(6)训练模型,同时保存原图像与随机生成的图像;

(7)展示原图像及重构图像

这是迭代30次的结果

图8-4 AVE构建图片
图8-4中,奇数列为原图像,偶数列为原图像重构的图像。从这个结果可以看出重构图像效果还不错。图8-5为由潜在空间通过解码器生成的新图像,这个图像效果也不错。
(8)显示由潜在空间点Z生成的新图像

图8-5 AVE新图片
这里构建网络主要用全连接层,有兴趣的读者,可以把卷积层,如果编码层使用卷积层(如nn.Conv2d),解码器需要使用反卷积层(nn. ConvTranspose2d)。接下来我们介绍生成式对抗网络,并用该网络生成新数字,其效果将好于AVE生成的数字。

8.2 GAN简介

上节我们介绍了基于自动编码器的AVE,根据这个网络可以生成新的图像。这节我们将介绍另一种生成式网络,它是基于博弈论的,所以又称为生成式对抗网络(Generative adversarial nets,GAN)。它是2014年由Ian Goodfellow提出的,它要解决的问题是如何从训练样本中学习出新样本,训练样本是图片就生成新图片,训练样本是文章就输出新文章等等。
GAN既不依赖标签来优化,也不是根据对结果奖惩来调整参数。它是依据生成器和判别器之间的博弈来不断优化。打个不一定很恰当的比喻,就像一台验钞机和一台制造假币的机器之间的博弈,两者不断博弈,博弈的结果假币越来越像真币,直到验钞机无法识别一张货币是假币还是真币为止。这样说,还是有点抽象,接下来我们将从多个侧面进行说明。

8.2.1 GAN架构

VAE利用潜在空间,可以生成连续的新图像,不过因损失函数采用像素间的距离,所以图像有点模糊。能否生成更清晰的新图像呢?可以的,这里我们用GAN替换VAE的潜在空间,它能够迫使生成图像与真实图像在统计上几乎无法区别的逼真合成图像。
GAN的直观理解,可以想象一个名画伪造者想伪造一幅达芬奇的画作,开始时,伪造者技术不精,但他,将自己的一些赝品和达芬奇的作品混在一起,请一个艺术商人对每一幅画进行真实性评估,并向伪造者反馈,告诉他哪些看起来像真迹、哪些看起来不像真迹。
伪造者根据这些反馈,改进自己的赝品。随着时间的推移,伪造者技能越来越高,艺术商人也变得越来越擅长找出赝品。最后,他们手上就拥有了一些非常逼真的赝品。
这就是GAN的基本原理。这里有两个角色,一个是伪造者,另一个是技术鉴赏者。他们训练的目的都是打败对方。
因此,GAN从网络的角度来看,它由两部分组成。
(1)生成器网络:它一个潜在空间的随机向量作为输入,并将其解码为一张合成图像。
(2)判别器网络:以一张图像(真实的或合成的均可)作为输入,并预测该图像来自训练集还是来自生成器网络。图8-6 为其架构图。

图8-6 GAN架构图
如何不断提升判别器辨别是非的能力?如何使生成的图片越来越像真图片?这些都通过控制它们各自的损失函数来控制。
训练结束后,生成器能够将输入空间中的任何点转换为一张可信图像。与VAE不同的是,这个潜空间无法保证带连续性或有特殊含义的结构。
GAN的优化过程不像通常的求损失函数的最小值,而是保持生成与判别两股力量的动态平衡。因此,其训练过程要比一般神经网络难很多。

8.2.2 GAN的损失函数

从GAN的架构图(图8-6)可知,控制生成器或判别器的关键是损失函数,如何定义损失函数成为整个GAN的关键。我们的目标很明确,既要不断提升判断器辨别是非或真假的能力,又要不断提升生成器不断提升图片质量,使判别器越来越难判别。这些目标如何用程序体现?损失函数就能充分说明。
为了达到判别器的目标,其损失函数既要考虑识别真图片能力,又要考虑识别假图片能力,而不能只考虑一方面,故判别器的损失函数为两者的和,具体代码如下:
D表示判别器,G为生成器,real_labels,fake_labels分别表示真图片标签、假图片标签。images是真图片,z是从潜在空间随机采样的向量,通过生成器得到假图片。

生成器的损失函数如何定义,才能使其越来越向真图片靠近?以真图片为标杆或标签即可。具体代码如下:

8.3用GAN生成图像

为便于说明GAN的关键环节,这里我们弱化了网络和数据集的复杂度。数据集为MNIST、网络用全连接层。后续我们将用一些卷积层的实例来说明。

8.3.1判别器

获取数据,导入模块基本与AVE的类似,这里就不展开来说,详细内容大家可参考char-08代码模块。
定义判别器网络结构,这里使用LeakyReLU为激活函数,输出一个节点并经过Sigmoid后输出,用于真假二分类。

8.3.2 生成器

生成器与AVE的生成器类似,不同的地方是输出为nn.tanh,使用nn.tanh将使数据分布在[-1,1]之间。其输入是潜在空间的向量z,输出维度与真图片相同。

8.3.3 训练模型

8.3.4 可视化结果

可视化每次由生成器得到假图片,即潜在向量z通过生成器得到的图片。

图8-7 GAN的新图片
图8-7明显好于图8-5。AVE生成图片主要依据原图片与新图片的交叉熵,而GAN真假图片的交叉熵,同时还兼顾了不断提升判别器和生成器本身的性能上。

8.4 VAE与GAN的异同

VAE适合于学习具有良好结构的潜在空间,潜在空间有比较好的连续性,其中存在一些有特定意义的方向。VAE能够捕捉到图像的结构变化(倾斜角度、圈的位置、形状变化、表情变化等)。这也是VAE的一个好处,它有显式的分布,能够容易地可视化图像的分布,具体如图8-8所示。

图8-8 AVE得到的数据流形分布图
GAN生成的潜在空间可能没有良好结构,但GAN生成的图像一般比VAE的更清晰。

7.7 Pytorch实现词性判别

我们知道每一个词都有词性,如train这个单词,可表示火车或训练等意思,具体表示为哪种词性,跟这个词所处的环境或上下文密切相关。要根据上下文来确定词性,正是循环网络擅长的事,因循环网络,尤其是LSTM或GRU网络,具有记忆功能。
这节将使用LSTM网络实现词性判别。

7.7.1 词性判别主要步骤

如何用LSTM对一句话里的各词进行词性标注?需要采用哪些步骤?这些问题就是这节将涉及的问题。用LSTM实现词性标注,我们可以采用以下步骤:
(1)实现词的向量化
假设有两个句子,作为训练数据,这两个句子的每个单词都已标好词性。当然我们不能直接把这两个语句直接输入LSTM模型,输入前需要把每个语句的单词向量化。假设这个句子共有5个单词,通过单词向量化后,就可得到序列[V_1, V_2, V_3, V_4, V_5],其中V_i表示第i个单词对应的向量。如何实现词的向量化?我们可以直接利用nn.Embedding层即可。当然在使用该层之前,需要把每句话对应单词或词性用整数表示。
(2)构建网络
词向量化之后,需要构建一个网络来训练,可以构建一个只有三层的网络,第一层为词嵌入层,第二层为LSTM层,最后一层用于词性分类的全连接层。
以下用Pytorch实现这些步骤。

7.7.2 数据预处理

(1)定义语句及词性
训练数据有两个语句,定义好每个词对应的词性。测试数据为一句话,没有指定词性。

(2)构建每个单词的索引字典
把每个单词用一个整数表示,将它们放在一个字典里。词性也如此。

手工设置词性的索引字典。
tag_to_ix = {"DET": 0, "NN": 1, "V": 2}

7.7.3 构建网络

构建训练网络,共三层,分别为嵌入层、LSTM层、全连接层。

其中有一个nn.Embedding(vocab_size, embed_dim)类,它是Module类的子类,这里它接受最重要的两个初始化参数:词汇量大小,每个词汇向量表示的向量维度。Embedding类返回的是一个形状为[每句词个数,词维度]的矩阵。nn.LSTM层的输入形状为(序列长度,批量大小,输入的大小),序列长度就是时间步序列长度,这个长度是可变的。F.log_softmax()执行的是一个Softmax回归的对数。
把数据转换为模型要求的格式,即把输入数据需要转换为torch.LongTensor张量。

7.7.4 训练网络

(1)定义几个超参数、实例化模型,选择损失函数、优化器等

(2)简单运行一次

['The', 'cat', 'ate', 'the', 'fish']
tensor([0, 1, 2, 3, 4])
tensor([[-1.4376, -0.9836, -0.9453],
[-1.4421, -0.9714, -0.9545],
[-1.4725, -0.8993, -1.0112],
[-1.4655, -0.9178, -0.9953],
[-1.4631, -0.9221, -0.9921]], grad_fn=)
(tensor([-0.9453, -0.9545, -0.8993, -0.9178, -0.9221], grad_fn=),
tensor([2, 2, 1, 1, 1]))
显然,这个结果不很理想。而下面我们循环多次训练该模型,精度将大大提升。
(3)训练模型

['The', 'cat', 'ate', 'the', 'fish']
tensor([[-4.9405e-02, -6.8691e+00, -3.0541e+00],
[-9.7177e+00, -7.2770e-03, -4.9350e+00],
[-3.0174e+00, -4.4508e+00, -6.2511e-02],
[-1.6383e-02, -1.0208e+01, -4.1219e+00],
[-9.7806e+00, -8.2493e-04, -7.1716e+00]], grad_fn=)
(tensor([-0.0494, -0.0073, -0.0625, -0.0164, -0.0008], grad_fn=),
tensor([0, 1, 2, 0, 1]))
这个精度为100%

7.7.5 测试模型

这里我们用另一句话,来测试这个模型

['They', 'ate', 'the', 'fish']
tensor([5, 2, 3, 4])
tensor([[-7.6594e+00, -5.2700e-03, -5.3424e+00],
[-2.6831e+00, -5.2537e+00, -7.6429e-02],
[-1.4973e-02, -1.0440e+01, -4.2110e+00],
[-9.7853e+00, -8.3971e-04, -7.1522e+00]], grad_fn=)
(tensor([-0.0053, -0.0764, -0.0150, -0.0008], grad_fn=),
tensor([1, 2, 0, 1]))
测试精度达到100%

7.8 用LSTM预测股票行情

这里采用沪深300指数数据,时间跨度为2010-10-10至今,选择每天最高价格。假设当天最高价依赖当天的前n(如30)天的沪深300的最高价。用LSTM模型来捕捉最高价的时序信息,通过训练模型,使之学会用前n天的最高价,判断当天的最高价(作为训练的标签值)。

7.8.1 导入数据

这里使用tushare来下载沪深300指数数据。可以用pip 安装tushare。

7.8.2 数据概览

(1)查看下载数据的字段、统计信息等。

图7-15 沪深300指数统计信息
从图7-15可知,共有2295条数据。
(2)可视化最高价数据

图7-16 可视化最高价

7.8.3 预处理数据

(1)生成训练数据

(2)规范化数据
#对数据进行预处理,规范化及转换为Tensor
df_numpy = np.array(df)

df_numpy_mean = np.mean(df_numpy)
df_numpy_std = np.std(df_numpy)

df_numpy = (df_numpy - df_numpy_mean) / df_numpy_std
df_tensor = torch.Tensor(df_numpy)

trainset = mytrainset(df_tensor)
trainloader = DataLoader(trainset, batch_size=batch_size, shuffle=False)

7.8.4 定义模型

这里使用LSTM网络,LSTM输出到一个全连接层。

7.8.5 训练模型

图7-17 batch-size=20的损失值变化情况
图7-17为batch-size=20时,损失值与迭代次数之间的关系,开始时振幅有点大,后面逐渐趋于平稳。如果batch-size变小,振幅可能更大。

7.8.6 测试模型

(1)使用测试数据,验证模型

(2)查看预测数据与源数据

图7-18 放大后预测数据与源数据比较
从图7-18 来看,预测结果还是不错的。