Chapter 2.1
神经网络初印象
这本笔记包含《深度学习》的第二章第一节的代码样本。
第一个例子是关于手写数字识别,看不懂没关系,在下一章会解释的。
首先,我们要解决的问题是给 28*28 的手写图像进行灰度值的分级(从 0 到 9),我们
要使用的数据库是 MNIST 的数据库,一个在机器学习里经典的数据库,它在这领域里已经被
深入研究过了。它包含 60000 個训练图像加上 10000 测试图像,您可以把处理 MNIST 问题看
做是深度学习里的“hello world”问题,这是用来验证您的算法是否如您所想。学了这门
课之后,您会反复看到 MINST 的。
MNIST 数据预装在 Keras,在四个一组的 NumPy 数组形式:
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) =
mnist.load_data()
train_images and train_labels 组成了训练集。test_images and test_labels 是测
试集。图像被编码成 Numpy 行列形式,特征将作为一列数,被从 0 到 9 排列。图像与特征之
间成一对一的对应关系。
我们来看下训练集
train_images.shape
(60000, 28, 28)
len(train_labels)
60000
train_labels
array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)
看下测试集
test_images.shape
(10000, 28, 28)
len(test_labels)
10000
test_labels
array([7, 2, 1, ..., 4, 5, 6], dtype=uint8)
我们的工作流程如下:首先我们用训练集来构建神经网络,网络将学着把图像与特征相
结合。最后我们让网络为测试集产生预估结果,我们将证实预估结果是否匹配测试集的特征。
现在让我们再来构建这个神经网络,记住,您不需要完全读懂这个例子。
构建神经网络模块的核心是“层”,它是一个您可以把它看成数据过滤器的数据处理模
块。一些数据进来,然后转化成一个更有用的形式输出。
更精确的来说,“层”提取出那些输进来的数据的特征,一些对手头的问题更具有价值
的特征。大多数的深度学习实际上是将简单的层链接在一起,从而实现一种渐进的“数据挖
掘”。一个深度学习模块就像一个数据处理的筛子,由一系列日益细化的数据过滤器构成,
即“层”。
我们的神经网络由两个全连接层组成,第二层是一个输出 10 個概率值的优化函数为
“softmax”的全连接层。每个值将是当前数字图像属于我们分的十个类别的数字类之一的
概率值。
为了让我们的神经网络适用于训练,我们还需要在编译部分选择三样东西。
1、合适的损失函数:
2、优化:这是一种机制,通过该机制,网络将根据其所看到的数据及其损失函数进行
自我更新
3、训练和测试期间要监控的指标。在这里,我们只关心准确度(正确分类的图像的分
数)。
损失函数和优化器的确切目的将在接下来的两章中明确说明。
network.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
在训练之前,我们将对数据进行预处理,将其整形为网络期望的形状,并将其缩放,使
所有值都在[ 0, 1 ]区间内。此前,我们的训练图像,例如存储在一个数组的形状(60000,
28, 28)在[0, 255]区间值型 uint8。我们把它变成一个形状 float32 阵列(60000, 28×28)
0 和 1 之间的值。
train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype('float32') / 255
我们还需要明确地对标签进行编码,这是我们在第 3 章中解释的步骤:
from keras.utils import to_categorical
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
开始训练
在训练过程中显示了两个数量:网络在训练数据上的“损失”,以及网络在训练数据上
的准确性。
我们很快就达到了训练数据的 0.989(即 98.9%)的准确率。现在让我们检查一下我们
的模型在测试集上是否表现良好:
我们的测试集的准确率是 97.8%,这比训练集的精确度要低一些。训练精度和测试精度
之间的这种差距是“过拟合”的一个例子,事实上,机器学习模型在处理新数据的时候表现
会比处理训练数据要差一点。过拟合将是第 3 章中的一个中心话题。
这就结束了我们的第一个例子——在不到 20 行的 Python 代码中,您只看到了如何构建
和训练一个神经网络来对手写数字进行分类。在下一章中,我们将详细地解释每一部分。您
将学习到 tensors 操作中的“tensors”,在网络中数据存储的物件、它是构成“层”的组成
成分,以及关于梯度下降的知识,这使我们的网络能够从它的训练示例中学习。
Chapter 3.5
电影评论分类:一个二进制分类的例子
二类分类或二元分类可能是应用最广泛的机器学习问题。在这个例子中,我们将学习根
据评论的文本内容将电影评论分类为“正面”评论和“负面”评论。
IMDB 数据集
我们将“IMDB 数据集”为处理对象,这一数据及是一组 50000 个的高度极化的,来自
互联网电影数据库的评论。用 25000 个评论来训练,用 25000 个来测试,每一组包括 50%个
负面和 50%个积极的评论。
为什么我们要把它们分为训练集和测试集?您不应该用用来训练机器的数据集来测试
这台机器。因为一个模型在训练数据集中表现良好并不意味着它会在自己从未见过的数据上
亦是如此,然而您真正需要的恰好是它能够在处理陌生数据时同样有较好的性能。例如,您
的模型可能只会记住您的训练样本和它们的目标之间的映射——这对于预测从未见过的目
标的任务是完全无用的。在下一章我们会重点讨论一下这方面的问题。
就像 MNIST 数据集一样,IMDB 数据集是 Keras 里面打包好的数据集。它已经经过了预
处理:评论(单词序列)已被转换成整数序列,其中每个整数代表字典中的特定单词。
下面的代码是加载数据集(第一次运行的视乎会有 80M 的数据被下载到您的机器中)
from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) =
imdb.load_data(num_words=10000)
num_words = 10000 意味着我们将只保留最常出现的 10000 个字作为训练数据。稀有的
词语将被丢弃。这使我们能够处理可管理大小的矢量数据。
变量 train_data and test_data 是评论列表,每一条评论转化成相应的文字
指数(代表那些文字),train_labels and test_labels 代表 0,1,0 指的是负面
评论,1 指的是正面评论。
因为我们限定了只保留最常出现的 10000 个字作为训练数据,所以没有对应文字指数的
大小会比 10000 大。
以下是如何快速解码这些评论中的一个回到英语单词:
# word_index is a dictionary mapping words to an integer index
word_index = imdb.get_word_index()
# We reverse it, mapping integer indices to words
reverse_word_index = dict([(value, key) for (key, value) in
word_index.items()])
# We decode the review; note that our indices were offset by 3
# because 0, 1 and 2 are reserved indices for "padding", "start of
sequence", and "unknown".
decoded_review = ' '.join([reverse_word_index.get(i - 3, '?') for i in
train_data[0]])
decoded_review
准备数据
我们不能将整数列表输入神经网络。我们必须把我们的名单变成张量。我们有两种方法
可以做到这一点:
1、 我们可以加长我们的名单,让它们都有相同的长度,并将其转化为形状的整数张量
(samples,word_indices),然后使用我们的网络中的第一层(嵌入层,我们将详细讨
论在本书的后面),一个能够处理这样的整数常数层。
2、 我们使用独热编码来将列表转化成 01 序列。具体来说,这就比如把序列[ 3, 5 ]为一
个 10000 维的向量,除了指数 3 和 5,其他都为零,这个向量将是一个二进制反码。然
后,我们可以使用一个能够处理浮点向量数的全连接层来作为我们构建的网络的第一层。
我们将选择第二种解决方法。让我们把数据矢量化,这样我们将尽可能手动地把问题弄
清楚:
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
# Create an all-zero matrix of shape (len(sequences), dimension)
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1.
# set specific indices of results[i]
to 1s
return results
# Our vectorized training data
x_train = vectorize_sequences(train_data)
# Our vectorized test data
x_test = vectorize_sequences(test_data)
以下是处理后的样本:
我们应该同样的将标签矢量化
现在我们的数据已经可以被送入神经网络中
构建我们的网络
我们的输入数据只是向量,我们的标签是标量(1 和 0):这是您遇到过的最简单的
设置。一种能够很好的解决这类问题的神经网络将会是由 ReLu 作为激活函数的全连接
层堆叠起来的:Dense(16, activation='relu')
在每一层全连接层(16)中传递的自变量是这一层中隐含单元的数量。什么是隐含
单元?它是这一层所表示的空间的维度。或许您会记得在上一章节中,每一个带有 Relu
激活函数的全连接层执行下面这一张量操作:
output = relu(dot(W, input) + b)
有 16 個隐含单元意味着权重矩阵 W 长这样:(input_dimension, 16)
比如:w 的点积将输入数据投影到 16 维表示空间上(然后我们会添加偏移向量 b 和运用
Relu 操作)。您可以直观地理解表示空间的维度,即“在学习内部表示时允许网络拥有
多少自由度”。拥有更多隐藏单元(更高维表示空间)可让您的网络学习更复杂的表示,
但它会使您的网络在计算上更加昂贵,并可能导致学习不需要的模式(模式可提高训练
数据的性能,但不会影响测试数据)。
关于这种密集层的堆栈有两个关键的体系结构决策:
使用多少层
为每一层选择多少个“隐藏单元”。
在下一章中,您将学习正式的原则来指导您做出这些选择。就目前而言,您必须选
择信任我们并选择以下架构:两个中间层,每个中间层有 16 个隐藏单元,第三个层将
输出关于当前评论情感的标量预测。中间层将 relu 用作它们的“激活函数”,并且最
后一层将使用 Sigmoid 激活函数来输出概率(0 到 1 之间的值,指示样本有多大可能具
有目标“1”,即评价有多可能是积极的)。一个 relu(整型线性单位)是一个函数,旨
在将负值清零,而一个 Sigmoid “压扁”任意值让它们降到到[0, 1] 区间内,从而输
出可被解释为概率的东西。
以下是我们的网络的样子:
这里是 Keras 的实现,与您之前看到的 MNIST 示例非常相似:
from keras import models
from keras import layers