人工智能与机器学习基础知识-三-

66 阅读42分钟

人工智能与机器学习基础知识(三)

原文:annas-archive.org/md5/0fdec4717e84cfdfa09316f6d2652a3f

译者:飞龙

协议:CC BY-NC-SA 4.0

第七章:使用神经网络进行深度学习

学习目标

到本章结束时,你将能够:

  • 执行基本的 TensorFlow 操作以解决各种表达式

  • 描述人工神经网络的工作原理

  • 使用 TensorFlow 训练和测试神经网络

  • 使用 TensorFlow 实现深度学习神经网络模型

在本章中,我们将使用 TensorFlow 库检测手写数字。

简介

在本章中,我们将学习另一种监督学习技术。然而,这次,我们不会使用像分类或回归这样的简单数学模型,而将使用一个完全不同的模型:神经网络。虽然我们将使用神经网络进行监督学习,但请注意,神经网络也可以模拟无监督学习技术。这个模型的重要性在上个世纪有所增加,因为在过去,使用这个模型进行监督学习所需的计算能力不足。因此,在上个世纪,神经网络在实践中应运而生。

TensorFlow for Python

TensorFlow 是 Google 维护的最重要的人工智能和开源库之一。TensorFlow API 在许多语言中可用,包括 Python、JavaScript、Java 和 C。由于 TensorFlow 支持监督学习,我们将使用 TensorFlow 构建图模型,然后使用此模型进行预测。

TensorFlow 与张量一起工作。张量的例子包括:

  • 标量值,例如浮点数。

  • 长度任意的向量。

  • 一个包含 p 乘 q 个值的常规矩阵,其中 p 和 q 是有限的整数。

  • 一个 p x q x r 的广义矩阵结构,其中 p、q、r 是有限的整数。想象这个结构在三维空间中是一个具有边长 p、q 和 r 的长方体对象。这个数据结构中的数字可以在三维空间中可视化。

  • 观察上述四种数据结构,更复杂、n 维数据结构也可以是张量的有效示例。

在本章中,我们将坚持使用标量、向量和常规矩阵张量。在本章范围内,将张量视为标量值、数组或数组的数组。

TensorFlow 用于创建人工神经网络,因为它模拟了其输入、输出、内部节点以及这些节点之间的有向边。TensorFlow 还附带数学函数来转换信号。当神经网络中的神经元被激活时,这些数学函数在建模时也会很有用。

注意

张量是类似数组的对象。流符号表示对张量数据的操作。因此,本质上,TensorFlow 是一个数组数据处理库。

TensorFlow 的主要用途是人工神经网络,因为这个领域需要对大数组和大矩阵进行操作。TensorFlow 附带了许多与深度学习相关的函数,因此它是神经网络的最佳环境。TensorFlow 用于语音识别、语音搜索,也是 translate.google.com 背后的大脑。在本章的后面部分,我们将使用 TensorFlow 来识别手写字符。

在 Anaconda Navigator 中安装 TensorFlow

让我们打开 Anaconda Prompt,使用pip安装 TensorFlow:

pip install tensorflow

安装需要几分钟,因为包本身相当大。如果你更喜欢使用你的显卡 GPU 而不是 CPU,你也可以使用tensorflow-gpu。确保只有在你有一个足够好的显卡时才使用 GPU 版本。

安装完成后,你可以在 IPython 中导入 TensorFlow:

import tensorflow as tf

首先,我们将使用 TensorFlow 构建一个图。这个模型的执行是分开的。这种分离很重要,因为执行是资源密集型的,因此可能需要在专门解决计算密集型问题的服务器上运行。

TensorFlow 操作

TensorFlow 提供了许多操作来操作数据。以下是一些这些操作的例子:

  • 算术运算

  • 指数运算explog

  • 关系运算大于小于等于

  • 数组操作concatslicesplit

  • 矩阵运算matrix_inversematrix_determinantmatmul

  • 神经网络相关操作sigmoidReLUsoftmax

练习 22:使用基本操作和 TensorFlow 常量

使用 TensorFlow 中的算术运算来解决表达式:2 * 3 + 4

这些操作可以用来构建图形。为了更深入地了解 TensorFlow 常量和基本算术运算符,让我们考虑一个简单的表达式2 * 3 + 4,这个表达式的图形如下:

图 7.1 表达式 2*3+4 的图形

图 7.1:表达式 2*3+4 的图形
  1. 使用以下代码在 TensorFlow 中模拟此图形:

    import tensorflow as tf
    input1 = tf.constant(2.0, tf.float32, name='input1')
    input2 = tf.constant(3.0, tf.float32, name='input2')
    input3 = tf.constant(4.0, tf.float32, name='input3')
    product12 = tf.multiply(input1, input2)
    sum = tf.add(product12, input3)
    
  2. 图形构建完成后,为了进行计算,我们必须打开一个 TensorFlow 会话并执行我们的节点:

    with tf.Session() as session:
        print(session.run(product12))
        print(session.run(sum))
    

    中间结果和最终结果将打印到控制台:

    6.0
    10.0
    

占位符和变量

现在你可以使用 TensorFlow 构建表达式了,让我们更进一步,构建占位符和变量。

当会话开始执行时,占位符会被替换为一个常量值。占位符本质上是在解决表达式之前被替换的参数。变量是在会话执行过程中可能发生变化的值。

让我们使用 TensorFlow 创建一个参数化表达式:

import tensorflow as tf
input1 = tf.constant(2.0, tf.float32, name='input1')
input2 = tf.placeholder(tf.float32, name='p')
input3 = tf.Variable(0.0, tf.float32, name='x')
product12 = tf.multiply(input1, input2)
sum = tf.add(product12, input3)
with tf.Session() as session:
    initializer = tf.global_variables_initializer()
    session.run(initializer)
    print(session.run(sum, feed_dict={input2: 3.0}))

输出是6.0

tf.global_variables_initializer()调用在session.run执行后,将input3中的变量初始化为其默认值,即零。

通过使用 feed 字典在另一个session.run语句中计算了总和,因此用常数3.0代替了input2参数。

注意,在这个特定示例中,变量 x 被初始化为零。在 TensorFlow 会话执行期间,x 的值不会改变。稍后,当我们使用 TensorFlow 来描述神经网络时,我们将定义一个优化目标,并且会话将优化变量的值以满足这个目标。

全局变量初始化器

由于 TensorFlow 经常使用矩阵运算,因此学习如何初始化一个随机变量的矩阵到一个以零为中心的正态分布随机生成的值是有意义的。

不仅矩阵,所有全局变量都是在会话内部通过调用tf.global_variables_initializer()来初始化的:

randomMatrix = tf.Variable(tf.random_normal([3, 4]))
with tf.Session() as session:
    initializer = tf.global_variables_initializer()
    print( session.run(initializer))
    print( session.run(randomMatrix))

None
[[-0.41974232 1.8810892 -1.4549098 -0.73987174]
[ 2.1072254 1.7968426 -0.38310152 0.98115194]
[-0.550108 -0.41858754 1.3511614 1.2387075 ]]

如您所见,tf.Variable的初始化需要一个参数:tf.random_normal([3,4])的值。

神经网络简介

神经网络是人工智能的最新分支。神经网络受到人类大脑工作方式的启发。最初,它们是在 20 世纪 40 年代由沃伦·麦卡洛克和沃尔特·皮茨发明的。神经网络是一个数学模型,用于描述人类大脑如何解决问题。

当我们谈论数学模型时,我们将使用“人工神经网络”这个短语,当我们谈论人类大脑时,我们将使用“生物神经网络”。人工神经网络是监督学习算法。

神经网络的学习方式比其他分类或回归模型更复杂。神经网络模型有很多内部变量,输入变量和输出变量之间的关系可能要通过多个内部层。与其他监督学习算法相比,神经网络具有更高的准确性。

注意

掌握 TensorFlow 中的神经网络是一个复杂的过程。本节的目的就是为你提供一个入门资源,帮助你开始学习。

在本章中,我们将使用的主要示例是从图像中识别数字。我们考虑这个图像是因为它很小,我们大约有 70,000 张图像可用。处理这些图像所需的处理能力与普通计算机相似。

人工神经网络的工作原理与人类大脑的工作原理相似。在人类大脑中,树突连接到细胞核,细胞核连接到轴突。在这里,树突充当输入,细胞核是计算发生的地方(加权总和和激活函数),轴突的作用类似于输出。

然后,我们通过将加权总和传递给激活函数来确定哪个神经元会激发。如果这个函数确定一个神经元必须激发,信号就会出现在输出中。这个信号可以是网络中其他神经元的输入:

图 7.2 展示人工神经网络工作原理的图

图 7.2:展示人工神经网络工作原理的图

假设f是激活函数,x1x2x3x4是输入,它们的和与权重w1w2w3w4相乘:

y = f(x1*w1 + x2*w2 + x3*w3 + x4*w4)

假设向量x是(x1x2x3x4),向量w是(w1w2w3w4),我们可以将这个方程写成这两个向量的标量或点积:

y = f(x ⋅ w)

我们定义的结构是一个神经元:

让我们隐藏这个神经元的细节,以便更容易构建神经网络:

图 7.4 表示神经元隐藏层的图

图 7.4:表示神经元隐藏层的图

我们可以创建多个框和多个输出变量,这些变量可能由于读取输入的加权平均值而被激活。

虽然在下面的图中,所有输入都指向所有框的箭头,但请记住,箭头上的权重可能为零。我们仍然在图中显示这些箭头:

图 7.5:表示神经网络的图

描述输入和输出之间关系的框被称为隐藏层。只有一个隐藏层的神经网络被称为常规神经网络

在连接输入和输出时,我们可能有多个隐藏层。具有多个层的神经网络被称为深度神经网络

图 7.6 表示深度神经网络的图

图 7.6:表示深度神经网络的图

深度学习这个术语来源于多层结构的存在。在创建人工神经网络时,我们可以指定隐藏层的数量。

偏置

让我们再次看看神经网络中神经元的模型:

图 7.7 神经网络中神经元的图

图 7.7:神经网络中神经元的图

我们了解到这个神经元的方程如下:

y = f(x1*w1 + x2*w2 + x3*w3 + x4*w4)

这个方程的问题是没有依赖于输入 x1,x2,x3 和 x4 的常数因子。这意味着神经网络中的每个神经元,如果没有偏置,总是会在每个权重-输入对的乘积为零时产生这个值。

因此,我们在方程中添加偏置:

y = f(x1*w1 + x2*w2 + x3*w3 + x4*w4 + b)
y = f(x ⋅ w + b)

第一个方程是详细形式,描述了每个坐标、权重系数和偏置的作用。第二个方程是向量形式,其中 x = (x1, x2, x3, x4)和 w = (w1, w2, w3, w4)。向量之间的点运算符表示两个向量的点积或标量积。这两个方程是等价的。我们将在实践中使用第二种形式,因为它比逐个定义每个变量更容易使用 TensorFlow 定义变量向量。

同样,对于w1w2w3w4,偏置b是一个变量,意味着它的值可以在学习过程中改变。

由于每个神经元都内置了这个常数因子,神经网络模型在拟合特定训练数据集方面变得更加灵活。

注意

可能会发生这样的情况,由于存在一些负权重,乘积 p = x1*w1 + x2*w2 + x3*w3 + x4*w4 是负的。我们可能仍然希望模型具有灵活性,以在超过给定负数的值上激活神经元。因此,添加一个常数偏置 b = 5,例如,可以确保神经元在 -5 到 0 之间的值上也能激活。

人工神经网络的用例

人工神经网络在监督学习技术中占有一席之地。它们可以模拟分类和回归问题。分类神经网络寻求特征和标签之间的关系。特征是输入变量,而分类器可以选择作为返回值的每个类别是一个单独的输出。在回归的情况下,输入变量是特征,而有一个单一的输出:预测值。虽然传统的分类和回归技术在人工智能中有其用例,但人工神经网络通常在寻找输入和输出之间的复杂关系方面表现得更好。

激活函数

神经网络中使用了不同的激活函数。没有这些函数,神经网络将是一个可以用矩阵乘法轻松描述的线性模型。

神经网络的激活函数提供了非线性。最常用的激活函数是 sigmoidtanh(双曲正切函数)。

sigmoid 的公式如下:

import numpy as np
def sigmoid(x):
    return 1 / (1 + np.e ** (-x))

让我们使用 pyplot 绘制这个函数:

import matplotlib.pylab as plt
x = np.arange(-10, 10, 0.1)
plt.plot(x, sigmoid(x))
plt.show()

输出如下:

图 7.8 显示 sigmoid 曲线的图形

图 7.8:显示 sigmoid 曲线的图形

Sigmoid 函数存在一些问题。

首先,它可能会不成比例地放大或衰减权重。

第二,sigmoid(0) 不为零。这使得学习过程更加困难。

双曲正切的公式如下:

def tanh(x):
    return 2 / (1 + np.e ** (-2*x)) - 1

我们也可以这样绘制这个函数:

x = np.arange(-10, 10, 0.1)
plt.plot(x, tanh(x))
plt.show()

输出如下:

图 7.9 绘制双曲正切后的图形

图 7.9:绘制双曲正切后的图形

这两个函数都给神经元发出的值增加了一点点非线性。sigmoid 函数看起来更平滑,而 tanh 函数给出稍微尖锐的结果。

最近另一种激活函数也变得流行起来:ReLUReLU 代表修正线性单元:

def relu(x):
    return 0 if x < 0 else x

使神经网络模型非线性化使得模型更容易逼近非线性函数。没有这些非线性函数,无论网络有多少层,我们都只能逼近线性问题:

def reluArr(arr):
   return [relu(x) for x in arr]
x = np.arange(-10, 10, 0.1)
plt.plot(x, reluArr(x))
plt.show()

输出如下:

图 7.10 显示 ReLU 函数的图形

图 7.10:显示 ReLU 函数的图形

从快速收敛到神经网络权重和偏差的最终值的角度来看,ReLU 激活函数表现得非常出色。

在本章中,我们将使用一个额外的函数:softmax

softmax 函数将列表中的值缩小到 01 之间,使得列表中所有元素的和变为 1softmax 函数的定义如下:

def softmax(list):
    return np.exp(list) / np.sum(np.exp(list))

这里有一个例子:

softmax([1,2,1])

输出结果如下:

array([0.21194156, 0.57611688, 0.21194156])

当我们过滤列表而不是单个值时,可以使用 softmax 函数。列表中的每个元素都将被转换。

让我们尝试不同的激活函数。观察这些函数如何通过解决以下练习来抑制加权的输入。

练习 23:激活函数

考虑以下神经网络:

y = f( 2 * x1 + 0.5 * x2 + 1.5 * x3 - 3 ).

假设 x1 是 1,x2 是 2,计算以下 x 值(-1,0,1,2)对应的 y 值:

  • f 是 sigmoid 函数

  • f 是 tanh 函数

  • f 是 ReLU 函数

执行以下步骤:

  1. 代入已知的系数:

    def get_y( f, x3 ):
        return f(2*1+0.5*2+1.5*x3)
    
  2. 使用以下三个激活函数:

    import numpy as np
    def sigmoid(x):
        return 1 / (1 + np.e ** (-x))
    def tanh(x):
        return 2 / (1 + np.e ** (-2*x)) - 1
    def relu(x):
        return 0 if x < 0 else x
    
  3. 使用以下命令计算 sigmoid 值:

    get_y( sigmoid, -2 )
    

    输出结果为 0.5

    get_y(sigmoid, -1)
    

    输出结果为 0.8175744761936437

    get_y(sigmoid, 0)
    

    输出结果为 0.9525741268224331

    get_y(sigmoid, 1)
    

    输出结果为 0.9890130573694068

    get_y(sigmoid, 2)
    

    输出结果为 0.9975273768433653

  4. 如您所见,当 sigmoid 函数内部的求和表达式增加时,变化迅速被抑制。我们期望 tanh 函数具有更大的抑制效果:

    get_y(tanh, -2)
    

    输出结果为 0.0

    get_y(tanh, -1)
    

    输出结果为 0.9051482536448663

    get_y(tanh, 0)
    

    输出结果为 0.9950547536867307

    get_y(tanh, 1)
    

    输出结果为 0.9997532108480274

    get_y(tanh, 2)
    

    输出结果为 0.9999877116507956

  5. 根据函数 tanh 的特性,输出比 sigmoid 函数更快地接近 1 的渐近线。对于 x3 = -2,我们计算 f(0)。而 sigmoid(0)0.5tanh(0)0。与另外两个函数不同,ReLu 函数不会抑制正值:

    get_y(relu,-2)
    

    输出结果为 0.0

    get_y(relu,-1)
    

    输出结果为 1.5

    get_y(relu,0)
    

    输出结果为 3.0

    get_y(relu,1)
    

    输出结果为 4.5

    get_y(relu,2)
    

    输出结果为 6.0

    ReLU 函数的另一个优点是,它的计算是所有激活函数中最简单的。

前向和反向传播

由于人工神经网络提供了一种监督学习技术,我们必须使用训练数据来训练我们的模型。训练网络的过程是找到属于每个变量输入对的权重。权重优化的过程包括重复执行两个步骤:前向传播和反向传播。

前向传播和反向传播这两个名称暗示了这些技术的工作方式。我们首先初始化神经网络箭头上的权重。然后,我们进行前向传播,接着进行反向传播。

前向传播根据输入值计算输出值。反向传播根据模型创建的标签值与训练数据中实际标签值之间的误差范围调整权重和偏差。权重调整的速率取决于神经网络的 学习率。学习率越高,反向传播期间权重和偏差的调整就越多。神经网络的动量决定了过去的结果如何影响权重和偏差的即将到来的值。

配置神经网络

以下参数通常用于创建神经网络:

  • 隐藏层的数量

  • 每个隐藏层的节点数

  • 激活函数

  • 学习率

  • 动量

  • 前向和反向传播的迭代次数

  • 错误容忍度

有一些经验法则可以用来确定每个隐藏层的节点数。如果你的隐藏层包含的节点数多于输入的大小,你可能会使模型过拟合。通常,节点数在输入和输出之间是合理的。

导入 TensorFlow 数字数据集

初看起来,识别手写数字似乎是一个简单的任务。然而,这个任务是一个具有十个可能标签值的简单分类问题。TensorFlow 提供了一个用于识别数字的示例数据集。

注意

你可以在 TensorFlow 网站上阅读有关此数据集的信息:www.tensorflow.org/tutorials/

我们将使用keras来加载数据集。你可以在 Anaconda Prompt 中使用以下命令进行安装:

pip install keras

记住,我们将对这些数据集进行监督学习,因此我们需要训练和测试数据:

import tensorflow.keras.datasets.mnist as mnist
(features_train, label_train),(features_test, label_test) =
mnist.load_ data()

特征是包含 28x28 图像像素值的数组。标签是介于 0 到 9 之间的一位整数。让我们看看第五个元素的特性和标签。我们将使用与上一节相同的图像库:

from PIL import Image
Image.fromarray(features_train[5])
图 7.11 训练图像
图 7.11:训练图像
label_train[5]
2

在本章末尾的活动结束时,你的任务将是创建一个神经网络,根据这些手写数字的值进行分类。

建模特征和标签

我们将通过 TensorFlow 数字数据集中识别手写数字的特征和标签建模示例来讲解。

我们以一个 28x28 像素的图像作为输入。每个图像的值要么是黑色,要么是白色。因此,特征集由一个 28 * 28 = 784 像素的向量组成。

图像为灰度图,颜色从 0 到 255 不等。为了处理它们,我们需要对数据进行缩放。通过将训练和测试特征除以 255.0,我们确保我们的特征缩放在 0 到 1 之间:

features_train = features_train / 255.0
features_test = features_test / 255.0

注意,我们可以有一个 28x28 的方阵来描述特征,但我们更愿意将矩阵展平并简单地使用一个向量。这是因为神经网络模型通常处理一维数据。

关于标签的建模,许多人认为用单个标签建模这个问题最有意义:一个介于 0 到 9 之间的整数值。这种方法是有问题的,因为计算中的小错误可能会导致完全不同的数字。我们可以想象,5 和 6 是相似的,所以相邻的值在这里工作得很好。然而,在 1 和 7 的情况下,一个小错误可能会让神经网络将 1 识别为 2,或者将 7 识别为 6。这非常令人困惑,并且可能需要更多的时间来训练神经网络以减少相邻值的错误。

更重要的是,当我们的神经网络分类器返回结果为 4.2 时,我们可能像《银河系漫游指南》中的英雄一样难以解释这个答案。4.2 很可能是 4。但如果不是,它可能是一个 5,或者一个 3,或者一个 6。这不是数字检测的工作方式。

因此,使用十个标签的向量来模拟这个任务更有意义。当使用 TensorFlow 进行分类时,为每个可能的类别创建一个标签,其值介于 0 和 1 之间,这是完全合理的。这些数字描述了读取的数字被分类为标签所代表的类别的成员的概率。

例如,值 [0, 0.1, 0, 0, 0.9, 0, 0, 0, 0, 0] 表示我们的数字有 90%的可能性是 4,有 10%的可能性是 2。

在分类问题中,我们总是为每个类别使用一个输出值。

让我们继续讨论权重和偏差。为了连接 28*28 = 784 个特征和 10 个标签,我们需要一个 784 行 10 列的权重矩阵。

因此,方程变为 y = f( x ⋅ W + b ) ,其中 x 是一个 784 维空间中的向量,W 是一个 784 x 10 的矩阵,b 是一个包含十个维度的偏差向量。y 向量也包含十个坐标。f 函数定义在具有十个坐标的向量上,并且应用于每个坐标。

注意

为了将二维 28x28 的数据点矩阵转换为 28x28 元素的单一维向量,我们需要展平矩阵。与许多其他语言和库不同,Python 没有展平方法。

由于展平是一个简单的任务,让我们构建一个展平方法:

def flatten(matrix):
    return [elem for row in matrix for elem in row]
flatten([[1,2],[3,4]])

输出如下:

 [1, 2, 3, 4]

让我们将 28*28 矩阵的特征展平到 784 维空间的向量:

features_train_vector = [
    flatten(image) for image in features_train
]
features_test_vector = [
    flatten(image) for image in features_test
]

为了将标签转换为向量形式,我们需要进行归一化:

import numpy as np
label_train_vector = np.zeros((label_train.size, 10))
for i, label in enumerate(label_train_vector):
    label[label_train[i]] = 1
label_test_vector = np.zeros((label_test.size, 10))
for i, label in enumerate(label_test_vector):
    label[label_test[i]] = 1

TensorFlow 多标签建模

我们现在将在 TensorFlow 中建模以下方程:y = f( x ⋅ W + b )

在导入 TensorFlow 后,我们将定义特征、标签和权重:

import tensorflow as tf
f = tf.nn.sigmoid
x = tf.placeholder(tf.float32, [None, 28 * 28])
W = tf.Variable(tf.random_normal([784, 10]))
b = tf.Variable(tf.random_normal([10]))

如果我们知道如何使用 TensorFlow 执行点积乘法,我们可以简单地写出方程 y = f( x ⋅ W + b )

如果我们将 x 视为一个1x84矩阵,我们可以使用tf.matmul函数将其与784x10的 W 矩阵相乘。

因此,我们的方程变为以下:y = f( tf.add( tf.matmul( x, W ), b ) )

你可能已经注意到 x 包含占位符,而 W 和 b 是变量。这是因为 x 的值是已知的。我们只需要在方程中替换它们。TensorFlow 的任务是优化 W 和 b 的值,以最大化我们读取正确数字的概率。

让我们将 y 的计算表达为一个函数形式:

def classify(x):
    return f(tf.add(tf.matmul(x, W), b))
注意

这是定义激活函数的地方。在本章末尾的活动结束时,你最好使用 softmax 激活函数。这意味着你将不得不在代码中将 sigmoid 替换为 softmax:f = tf.nn.softmax

优化变量

占位符代表输入。TensorFlow 的任务是优化变量。

为了执行优化,我们需要使用一个成本函数:交叉熵。交叉熵具有以下特性:

  • 如果预测输出与实际输出匹配,其值为零

  • 其值在之后是严格正的

我们的任务是最小化交叉熵:

y = classify(x)
y_true = tf.placeholder(tf.float32, [None, 10])
cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(
    logits=y,
    labels=y_true
)

虽然计算 y 的函数被称为 classify,但我们在这里并没有执行实际的分类。记住,我们正在使用占位符代替 x,实际值在运行 TensorFlow 会话时被替换。

sigmoid_cross_entropy_with_logits函数接受两个参数来比较它们的值。第一个参数是标签值,而第二个参数是预测结果。

为了计算成本,我们必须调用 TensorFlow 的reduce_mean方法:

cost = tf.reduce_mean(cross_entropy)

成本最小化通过一个优化器进行。我们将使用带有学习率的GradientDescentOptimizer。学习率是影响模型调整速度的神经网络参数:

optimizer = tf.train.GradientDescentOptimizer(learning_rate = 0.5).minimize(cost)

在这个阶段不执行优化,因为我们还没有运行 TensorFlow。我们将在主循环中执行优化。

如果你使用的是不同的激活函数,如 softmax,你将不得不在源代码中替换它。而不是以下语句:

cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(
    logits=y,
    labels=y_true
)

使用以下内容:

cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(

logits=y,

labels=y_true

)

注意

方法名称中的 _v2 后缀。这是因为原始的tf.nn.softmax_cross_entropy_with_logits方法已被弃用。

训练 TensorFlow 模型

我们需要创建一个 TensorFlow 会话并运行模型:

session = tf.Session()

首先,我们使用tf.global_variables_initializer()初始化变量:

session.run(tf.global_variables_initializer())

然后是优化循环。我们将确定迭代次数和批量大小。在每次迭代中,我们将随机选择与批量大小相等的特征-标签对。

为了演示目的,我们不会创建随机批次,而是在每次新迭代开始时简单地提供即将到来的前一百张图像。

由于我们总共有 60,000 张图像,我们可能会有多达 300 次迭代和每次迭代 200 张图像。实际上,我们只会运行几次迭代,这意味着我们只会使用部分可用的训练数据:

iterations = 300
batch_size = 200
for i in range(iterations):
    min = i * batch_size
    max = (i+1) * batch_size
    dictionary = {
        x: features_train_vector[min:max],
        y_true: label_train_vector[min:max]
    }
    session.run(optimizer, feed_dict=dictionary)
    print('iteration: ', i)

使用模型进行预测

我们现在可以使用训练好的模型进行预测。语法很简单:我们将测试特征输入到会话的字典中,并请求classify(x)值:

session.run(classify(x), feed_dict={
    x: features_test_vector[:10]
} )

测试模型

现在我们已经训练了模型,并且可以使用它进行预测,是时候测试其性能了:

label_predicted = session.run(classify(x), feed_dict={
    x: features_test_vector
})

我们必须通过从每个结果中取最大值的索引,将labelsPredicted值转换回 0 到 9 的整数范围。我们将使用 NumPy 函数来完成这个转换。

argmax函数返回其列表或数组参数中具有最大值的索引。以下是一个示例:

np.argmax([0.1, 0.3, 0.5, 0.2, 0, 0, 0, 0.2, 0, 0 ])

输出是2

这里是带有argmax函数的第二个示例

np.argmax([1, 0, 1])

输出是0

让我们进行转换:

label_predicted = [
    np.argmax(label) for label in label_predicted
]

我们可以使用我们在前几章中学到的 scikit-learn 中的度量。让我们先计算混淆矩阵:

from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score
confusion_matrix(label_test, label_predicted)
accuracy_score(label_test, label_predicted)
precision_score( label_test, label_predicted, average='weighted' )
recall_score( label_test, label_predicted, average='weighted' )
f1_score( label_test, label_predicted, average='weighted' )

随机化样本大小

回忆神经网络的训练函数:

iterations = 300
batch_size = 200
for i in range(iterations):
    min = i * batch_size
    max = (i+1) * batch_size
    dictionary = {
        x: features_train_vector[min:max],
        y_true: label_train_vector[min:max]
    }
    session.run(optimizer, feed_dict=dictionary)

问题在于,在 60,000 个数字中,我们只能进行 5 次迭代。如果我们想超过这个阈值,我们就会面临重复这些输入序列的风险。

我们可以通过随机选择训练数据中的值来最大化使用训练数据的有效性。

我们可以使用random.sample方法来完成这个目的:

iterations = 6000
batch_size = 100
sample_size = len(features_train_vector)
for _ in range(iterations):
    indices = random.sample(range(sample_size), batchSize)
    batch_features = [
        features_train_vector[i] for i in indices
    ]
    batch_labels = [
        label_train_vector[i] for i in indices
    ]
    min = i * batch_size
    max = (i+1) * batch_size
    dictionary = {
        x: batch_features,
        y_true: batch_labels
    }
    session.run(optimizer, feed_dict=dictionary)
注意

随机样本方法从列表中随机选择给定数量的元素。例如,在匈牙利,主要的国家级彩票是基于从 90 个数字中选择 5 个数字。我们可以使用以下表达式来模拟一轮彩票:

import random
random.sample(range(1,91), 5)

输出如下:

[63, 58, 25, 41, 60]

活动 14:手写数字检测

在本节中,我们将讨论如何通过检测手写数字为加密货币交易者提供更多安全性。我们将假设你是一家新加密货币交易平台上的软件开发者。你正在实施的最新安全措施需要识别手写数字。使用 MNIST 库训练一个神经网络来识别数字。你可以在www.tensorflow.org/tutorials/了解更多关于这个数据集的信息。

通过执行以下步骤尽可能提高模型的准确度:

  1. 加载数据集并格式化输入。

  2. 设置 TensorFlow 图。现在我们将使用ReLU函数而不是 sigmoid 函数。

  3. 训练模型。

  4. 测试模型并计算准确度得分。

  5. 通过重新运行负责训练数据集的代码段,我们可以提高其准确性。运行代码 50 次。

  6. 打印混淆矩阵。

在第五十次运行结束时,混淆矩阵已经改进。

这不是一个坏的结果。超过 8 个数字被准确识别。

注意

这个活动的解决方案可以在第 298 页找到。

如你所见,神经网络并不呈线性提高。可能看起来训练网络在一段时间内对准确性的增量改进很小或没有。然而,在达到某个阈值后,会出现突破,准确度会大幅提高。

这种行为与人类学习相似。你可能现在也会在神经网络方面遇到困难。然而,在深入材料并尝试一些练习之后,你将实现一次又一次的突破,你的进步将加速。

深度学习

在这个主题中,我们将增加神经网络的层数。你可能记得我们可以向我们的图中添加隐藏层。我们将通过实验隐藏层来提高我们模型的准确性。

添加层

回想一下具有两个隐藏层的神经网络图:

图 7.12 展示神经网络中两个隐藏层的图

图 7.12:展示神经网络中两个隐藏层的图

我们可以通过复制权重和偏差并确保 TensorFlow 变量的维度匹配来在等式中添加第二个层。注意,在第一个模型中,我们将 784 个特征转换成了 10 个标签。

在这个模型中,我们将把 784 个特征转换成指定数量的输出。然后我们将这些输出转换成 10 个标签。

确定添加的隐藏层的节点数并不完全是科学。在这个例子中,我们将使用 200 个节点,因为它位于特征和标签维度之间。

由于我们有两个层,我们将定义两个矩阵(W1, W2)和向量(b1, b2)来分别表示权重和偏差。

首先,我们使用W1b1减少 784 个输入点,并创建 200 个变量值。我们将这些值作为第二层的输入,并使用W2b2创建 10 个标签值:

x = tf.placeholder(tf.float32, [None, 28 * 28 ])
f = tf.nn.softmax
W1 = tf.Variable(tf.random_normal([784, 200]))
b1 = tf.Variable(tf.random_normal([200]))
layer1_out = f(tf.add( tf.matmul(x, W1), b1))
W2 = tf.Variable(tf.random_normal([200, 10]))
b2 = tf.Variable(tf.random_normal([10]))
y = f(tf.add(tf.matmul(layer1_out, W2), b2))

如果需要,我们可以这样增加层数。层 n 的输出必须是层 n+1 的输入。其余的代码保持不变。

卷积神经网络

卷积神经网络CNNs)是针对模式识别优化的人工神经网络。CNNs 基于深度神经网络中的卷积层。卷积层由使用卷积操作转换其输入的神经元组成。

当使用卷积层时,我们使用一个 mn 的矩阵在图像中检测模式,其中 m 和 n 分别小于图像的宽度和高度。在执行卷积操作时,我们将这个 mn 矩阵在图像上滑动,匹配每一个可能性。我们计算 m*n 卷积滤波器与当前卷积滤波器所在的 3x3 图像像素值的标量积。卷积操作从原始图像创建一个新的图像,其中我们图像的重要方面被突出显示,不那么重要的方面则被模糊化。

卷积操作总结了它所观察到的窗口上的信息。因此,它是一个识别图像中形状的理想操作符。形状可以出现在图像的任何位置,卷积操作符识别相似图像信息,无论其确切位置和方向如何。卷积神经网络超出了本书的范围,因为它是一个更高级的话题。

活动十五:使用深度学习进行手写数字检测

在本节中,我们将讨论深度学习如何提高你模型的表现。我们假设你的老板对你提交的第 14 个活动的结果不满意,并要求你考虑在你的原始模型中添加两个隐藏层,以确定新层是否能提高模型的准确性。为了确保你能正确完成这个活动,你需要对深度学习有所了解:

  1. 执行上一个活动的步骤,并测量模型的准确率。

  2. 通过添加新层来改变神经网络。我们将结合ReLUsoftmax激活函数。

  3. 重新训练模型。

  4. 评估模型。找到准确率分数。

  5. 运行代码 50 次。

  6. 打印混淆矩阵。

这个深度神经网络比单层神经网络表现得更加混沌。它经过 600 次迭代,每次迭代 200 个样本,从准确率 0.572 提升到 0.5723。不久之后,在相同的迭代次数中,准确率从 0.6076 跃升至 0.6834。

由于深度神经网络的灵活性,我们预计达到准确率上限的时间会比简单模型晚。由于深度神经网络的复杂性,它也更可能长时间陷入局部最优。

注意

这个活动的解决方案可以在第 302 页找到。

摘要

在这本书中,我们在“人工智能原理”章节学习了人工智能的基础知识以及人工智能的应用,然后我们编写了 Python 代码来模拟井字棋游戏。

在“使用搜索技术和游戏的人工智能”这一章节中,我们使用游戏人工智能工具和搜索技术解决了井字棋游戏。我们学习了广度优先搜索和深度优先搜索的搜索算法。A*算法帮助学生建模路径查找问题。这一章节以建模多人游戏作为结束。

在接下来的几章中,我们学习了使用回归和分类进行监督学习。这些章节包括数据预处理、训练-测试分割以及在几个实际场景中使用的模型。当预测股票数据时,线性回归、多项式回归和支持向量机都非常有用。分类使用了 k 近邻和支持向量机分类器。几个活动帮助学生将分类的基本原理应用于一个有趣的现实生活用例:信用评分。

第五章使用树进行预测分析中,我们介绍了决策树、随机森林和超随机树。本章介绍了评估模型效用性的不同方法。我们学习了如何计算模型的准确率、精确率、召回率和 F1 分数。我们还学习了如何创建模型的混淆矩阵。本章的模型通过评估汽车数据得到了实际应用。

第六章聚类中介绍了无监督学习,以及 k 均值和均值漂移聚类算法。这些算法的一个有趣方面是,标签不是预先给出的,而是在聚类过程中检测到的。

本书以第七章,使用神经网络进行深度学习结束,其中介绍了神经网络和 TensorFlow 的深度学习。我们使用这些技术在现实生活中的一个例子上应用了这些技术:手写数字的检测。

附录

关于

本节包含以帮助学生执行书中活动的内容。它包括学生为实现活动目标需要执行的详细步骤。

第一章:人工智能原理

在代码中,反斜杠(\)表示换行,当代码一行放不下时使用。行尾的反斜杠会转义换行符。这意味着在反斜杠后面的内容应该被读取为从反斜杠字符开始的内容。

活动一:生成井字棋游戏中所有可能的步骤序列

本节将探讨当两位玩家随机出牌时可能出现的组合爆炸。我们将使用一个程序,基于之前的结果生成计算机玩家和人类玩家之间所有可能的移动序列。确定根据行动序列的不同,胜负和平局的数量。假设人类玩家可以做出任何可能的移动。在这个例子中,由于计算机玩家是随机出牌,我们将检查两个随机出牌玩家的胜负和平局:

  1. 创建一个函数,将 all_moves_from_board 函数映射到每个棋盘列表的元素上。这样,我们将在每个深度上拥有决策树的全部节点:

    def all_moves_from_board(board, sign):
        move_list = []
        for i, v in enumerate(board):
            if v == EMPTY_SIGN:
                move_list.append(board[:i] + sign + board[i+1:])
        return move_list
    
  2. 决策树从 [ EMPTY_SIGN * 9 ] 开始,并在每一步之后扩展:

    all_moves_from_board_list( [ EMPTY_SIGN * 9 ], AI_SIGN )
    
  3. 输出如下:

     ['X........',
     '.X.......',
     '..X......',
     '...X.....',
     '....X....',
     '.....X...',
     '......X..',
     '.......X.',
     '........X']
     ['XO.......',
     'X.O......',
     'X..O.....',
     'X...O....',
     'X....O...',
     'X.....O..',
     'X......O.',
    .
    .
    .
    .
    '......OX.',
     '.......XO',
     'O.......X',
     '.O......X',
     '..O.....X',
     '...O....X',
     '....O...X',
     '.....O..X',
     '......O.X',
     '.......OX']
    
  4. 让我们创建一个 filter_wins 函数,它从移动列表中移除结束的游戏,并将包含 AI 玩家和对手玩家赢得的棋盘状态的数组附加到其中:

    def filter_wins(move_list, ai_wins, opponent_wins):
        for board in move_list:
            won_by = game_won_by(board)
            if won_by == AI_SIGN:
                ai_wins.append(board)
                move_list.remove(board)
            elif won_by == OPPONENT_SIGN:
                opponent_wins.append(board)
                move_list.remove(board)
    
  5. 在这个函数中,三个列表可以被视为引用类型。这意味着函数不返回值,而是操作这三个列表而不返回它们。

  6. 让我们完成这一节。然后使用一个 count_possibilities 函数,打印出以平局结束、第一玩家获胜和第二玩家获胜的决策树叶子的数量:

    def count_possibilities():
        board = EMPTY_SIGN * 9
        move_list = [board]
        ai_wins = []
        opponent_wins = []
        for i in range(9):
            print('step ' + str(i) + '. Moves: ' + \        str(len(move_list)))
            sign = AI_SIGN if i % 2 == 0 else OPPONENT_SIGN
            move_list = all_moves_from_board_list(move_list, sign)
            filter_wins(move_list, ai_wins, opponent_wins)
        print('First player wins: ' + str(len(ai_wins)))
        print('Second player wins: ' + str(len(opponent_wins)))
        print('Draw', str(len(move_list)))
        print('Total', str(len(ai_wins) + len(opponent_wins) + \    len(move_list)))
    
  7. 在每个状态下,我们最多有 9 步。在 0th、2nd、4th、6th 和 8th 次迭代中,AI 玩家移动。在所有其他迭代中,对手移动。我们在所有步骤中创建所有可能的移动,并从移动列表中移除结束的游戏。

  8. 然后执行可能性数量以体验组合爆炸。

    count_possibilities()
    
  9. 输出如下:

    step 0\. Moves: 1
    step 1\. Moves: 9
    step 2\. Moves: 72
    step 3\. Moves: 504
    step 4\. Moves: 3024
    step 5\. Moves: 13680
    step 6\. Moves: 49402
    step 7\. Moves: 111109
    step 8\. Moves: 156775
    First player wins: 106279
    Second player wins: 68644
    Draw 91150
    Total 266073
    

如您所见,棋盘状态的树由 266,073 个叶子组成。count_possibilities函数本质上实现了一个广度优先搜索算法来遍历游戏的所有可能状态。请注意,我们确实多次计算了这些状态,因为第 1 步在右上角放置 X,第 3 步在左上角放置 X,会导致与从左上角开始然后放置右上角 X 相似的可能状态。如果我们实现了重复状态的检测,我们就需要检查更少的节点。然而,在这个阶段,由于游戏的深度有限,我们省略了这个步骤。

第二章:具有搜索技术和游戏的 AI

活动二:教会智能体识别它防守损失的情况

按照以下步骤完成活动:

  1. 创建一个名为player_can_win的函数,它使用all_moves_from_board函数从棋盘上获取所有移动,并使用变量next_move遍历它。在每次迭代中,它检查游戏是否可以通过标记获胜,然后返回 true 否则返回 false。

    def player_can_win(board, sign):
        next_moves = all_moves_from_board(board, sign)
        for next_move in next_moves:
            if game_won_by(next_move) == sign:
                return True
        return False
    
  2. 我们将扩展 AI 的移动,使其更喜欢安全的移动。一个移动是安全的,如果对手在下一步不能赢得比赛。

    def ai_move(board):
        new_boards = all_moves_from_board(board, AI_SIGN)
        for new_board in new_boards:
            if game_won_by(new_board) == AI_SIGN:
                return new_board
        safe_moves = []
        for new_board in new_boards:
            if not player_can_win(new_board, OPPONENT_SIGN):
                safe_moves.append(new_board)
        return choice(safe_moves) if len(safe_moves) > 0 else \        new_boards[0]
    
  3. 您可以测试我们的新应用程序。您会发现 AI 已经做出了正确的移动。

  4. 我们现在将这个逻辑放入状态空间生成器中,并通过生成所有可能的比赛来检查计算机玩家的表现。

    def all_moves_from_board( board, sign ):
    
  5. 我们现在将这个逻辑放入状态空间生成器中,并通过生成所有可能的比赛来检查计算机玩家的表现。

    def all_moves_from_board(board, sign):
        move_list = []
        for i, v in enumerate(board):
            if v == EMPTY_SIGN:
                new_board = board[:i] + sign + board[i+1:]
                move_list.append(new_board)
                if game_won_by(new_board) == AI_SIGN:
                    return [new_board]
        if sign == AI_SIGN:
            safe_moves = []
            for move in move_list:
                if not player_can_win(move, OPPONENT_SIGN):
                    safe_moves.append(move)
            return safe_moves if len(safe_moves) > 0 else \            move_list[0:1]
        else:
            return move_list
    
  6. 计算所有可能的情况。

    count_possibilities()
    
  7. 输出如下:

    step 0\. Moves: 1
    step 1\. Moves: 9
    step 2\. Moves: 72
    step 3\. Moves: 504
    step 4\. Moves: 3024
    step 5\. Moves: 5197
    step 6\. Moves: 18606
    step 7\. Moves: 19592
    step 8\. Moves: 30936
    
    First player wins: 20843
    Second player wins: 962
    Draw 20243
    Total 42048
    

我们的表现比以前更好。我们不仅又消除了几乎三分之二的可能游戏,而且大多数时候,AI 玩家要么获胜,要么接受平局。尽管我们努力使 AI 变得更好,但它仍然有 962 种方式会输掉比赛。我们将在下一个活动中消除所有这些损失。

活动三:固定 AI 的前两个移动使其无敌

按照以下步骤完成活动:

  1. 我们将计算棋盘上空格的数量,并在有 9 个或 7 个空格的情况下进行硬编码的移动。您可以尝试不同的硬编码移动。我们发现占据任何角落,然后占据对角角落会导致没有损失。如果对手占据了对角角落,然后在中间移动会导致没有损失。

    def all_moves_from_board(board, sign):
        if sign == AI_SIGN:
            empty_field_count = board.count(EMPTY_SIGN)
            if empty_field_count == 9:
                return [sign + EMPTY_SIGN * 8]
            elif empty_field_count == 7:
                return [
                    board[:8] + sign if board[8] == \                    EMPTY_SIGN else
                    board[:4] + sign + board[5:]
                ]
        move_list = []
        for i, v in enumerate(board):
            if v == EMPTY_SIGN:
                new_board = board[:i] + sign + board[i+1:]
                move_list.append(new_board)
                if game_won_by(new_board) == AI_SIGN:
                    return [new_board]
        if sign == AI_SIGN:
    
            safe_moves = []
            for move in move_list:
                if not player_can_win(move, OPPONENT_SIGN):
                    safe_moves.append(move)
            return safe_moves if len(safe_moves) > 0 else \            move_list[0:1]
        else:
            return move_list
    
  2. 让我们验证状态空间

    countPossibilities()
    
  3. 输出如下:

    step 0\. Moves: 1
    step 1\. Moves: 1
    step 2\. Moves: 8
    step 3\. Moves: 8
    step 4\. Moves: 48
    step 5\. Moves: 38
    step 6\. Moves: 108
    step 7\. Moves: 76
    step 8\. Moves: 90
    First player wins: 128
    Second player wins: 0
    Draw 60
    
  4. 在固定前两个步骤之后,我们只需要处理 8 种可能性,而不是 504 种。我们还引导 AI 进入一个状态,其中硬编码的规则足以确保永远不会输掉比赛。

  5. 固定步骤并不重要,因为我们将会给 AI 提供硬编码的步骤作为起始点,但它是重要的,因为它是一个评估和比较每个步骤的工具。

  6. 在修复了前两步之后,我们只需要处理 8 种可能性,而不是 504 种。我们还引导 AI 进入一个状态,其中硬编码的规则足以确保永远不会输掉游戏。

活动 4:四子棋

本节将练习使用EasyAI库并开发启发式方法。我们将使用四子棋游戏。游戏板宽度为 7 个单元格,高度为 6 个单元格。当你移动时,你只能选择放下你的标记的列。然后重力将标记拉到最低的空单元格。你的目标是先于对手水平、垂直或对角线连接四个自己的标记,或者你用完所有空位。游戏规则可以在en.wikipedia.org/wiki/Connect_Four找到。

  1. 让我们设置 TwoPlayersGame 框架:

    from easyAI import TwoPlayersGame
    from easyAI.Player import Human_Player
    class ConnectFour(TwoPlayersGame):
        def __init__(self, players):
            self.players = players
        def possible_moves(self):
            return []
        def make_move(self, move):
            return
        def unmake_move(self, move):
    # optional method (speeds up the AI)
            return
        def lose(self):
            return False
        def is_over(self):
            return (self.possible_moves() == []) or self.lose()
        def show(self):
            print ('board')
        def scoring(self):
            return -100 if self.lose() else 0
    
    if __name__ == "__main__":
        from easyAI import AI_Player, Negamax
        ai_algo = Negamax(6)
    
  2. 我们可以保留定义中的一些函数不变。我们必须实现以下方法:

    __init__
    possible_moves
    make_move
    unmake_move (optional)
    lose
    show
    
  3. 我们将重用 tic-tac-toe 中的基本评分函数。一旦你测试了游戏,你会发现游戏并非不可战胜,尽管我们只使用了基本的启发式方法,但游戏表现却出人意料地好。

  4. 让我们编写 init 方法。我们将定义棋盘为一个一维列表,类似于 tic-tac-toe 示例。我们也可以使用二维列表,但建模不会变得容易或困难很多。除了像 tic-tac-toe 游戏中那样进行初始化之外,我们还会稍微提前工作。我们将生成游戏中所有可能的获胜组合,并将它们保存供将来使用:

    def __init__(self, players):
            self.players = players
            # 0 1 2 3 4 5 6
            # 7 8 9 10 11 12 13
            # ...
            # 35 36 37 38 39 40 41
            self.board = [0 for i in range(42)]
            self.nplayer = 1 # player 1 starts.
            def generate_winning_tuples():
                tuples = []
                # horizontal
                tuples += [
                    list(range(row*7+column, row*7+column+4, 1))
                    for row in range(6)
                    for column in range(4)]
                # vertical
                tuples += [
                    list(range(row*7+column, row*7+column+28, 7))
                    for row in range(3)
                    for column in range(7)
                ]
    
                # diagonal forward
                tuples += [
                    list(range(row*7+column, row*7+column+32, 8))
                    for row in range(3)
                    for column in range(4)
                ]
                # diagonal backward
                tuples += [
                    list(range(row*7+column, row*7+column+24, 6))
                    for row in range(3)
                    for column in range(3, 7, 1)
                ]
                return tuples
    self.tuples=generate_winning_tuples()
    
  5. 让我们处理移动。可能的移动函数是一个简单的枚举。注意我们在移动名称中使用从 1 到 7 的列索引,因为在人类玩家界面中从 1 开始列索引比从 0 开始更方便。对于每一列,我们检查是否存在未占用的字段。如果有一个,我们将该列设为可能的移动。

    def possible_moves(self):
            return [column+1
                    for column in range(7)
                    if any([
                        self.board[column+row*7] == 0
                        for row in range(6)
                    ])
                    ]
    
  6. 进行移动与可能的移动函数类似。我们检查移动的列,并从底部开始找到第一个空单元格。一旦找到,我们就占据它。你也可以阅读 make_move 函数的逆函数:unmake_move 的实现。在 unmake_move 函数中,我们从顶部到底部检查列,并在第一个非空单元格处移除移动。注意我们依赖于 easyAi 的内部表示,这样它就不会撤销它没有做出的移动。否则,这个函数会移除其他玩家的标记,而不会检查是哪个玩家的标记被移除。

    def make_move(self, move):
            column = int(move) - 1
            for row in range(5, -1, -1):
                index = column + row*7
                if self.board[index] == 0:
                    self.board[index] = self.nplayer
                    return
        def unmake_move(self, move):
    # optional method (speeds up the AI)
            column = int(move) - 1
            for row in range(6):
                index = column + row*7
                if self.board[index] != 0:
                    self.board[index] = 0
                    return
    
  7. 我们已经有了需要检查的元组,因此我们可以大部分重用 tic-tac-toe 示例中的 lose 函数。

    def lose(self):
            return any([all([(self.board[c] == self.nopponent)
                             for c in line])
                        for line in self.tuples])
        def is_over(self):
            return (self.possible_moves() == []) or self.lose()
    
  8. 我们最后一个任务是 show 方法,它将打印出棋盘。我们将重用 tic-tac-toe 的实现,并仅更改变量。

    def show(self):
            print('\n'+'\n'.join([
                ' '.join([['.', 'O', 'X'][self.board[7*row+column]]
                         for column in range(7)]
                         )
                for row in range(6)])
            )
    

现在所有函数都已完成,你可以尝试一下示例。请随意与对手玩几轮。你可以看到对手并不完美,但它的表现相当不错。如果你有一台强大的计算机,你可以增加 Negamax 算法的参数。我鼓励你提出更好的启发式方法。

第三章:回归

活动 5:预测人口

你在 Metropolis 政府办公室工作,试图预测小学容量需求。你的任务是确定 2025 年和 2030 年开始上小学的儿童数量预测。以下为历史数据:

图 3.21:小学数据

在二维图表上绘制趋势。使用线性回归。

我们的特征是 2001 年至 2018 年的年份。为了简单起见,我们可以将 2001 年表示为第 1 年,2018 年表示为第 18 年。

x = np.array(range(1, 19))
y = np.array([
    147026,
    144272,
    140020,
    143801,
    146233,
    144539,
    141273,
    135389,
    142500,
    139452,
    139722,
    135300,
    137289,
    136511,
    132884,
    125683,
    127255,
    124275
])

使用 np.polyfit 来确定回归线的系数。

[a, b] = np.polyfit(x, y, 1)
[-1142.0557275541753, 148817.5294117646]

使用 matplotlib.pyplot 绘制结果以确定未来的趋势。

import matplotlib.pyplot as plot
plot.scatter( x, y )
plot.plot( [0, 30], [b, 30*a+b] )
plot.show()

活动 6:使用多元二次和三次线性多项式回归预测股价

本节将讨论如何使用 scikit-learn 执行线性、多项式和支持向量回归。我们还将学习预测给定任务的最佳拟合模型。我们将假设你是金融机构的一名软件工程师,你的雇主想知道线性回归或支持向量回归哪个更适合预测股价。你必须从数据源中加载所有 S&P 500 的数据。然后使用线性回归、三次多项式线性回归和具有三次多项式核的支持向量回归构建回归器。然后分离训练数据和测试数据。绘制测试标签和预测结果,并与 y = x 线进行比较。最后,比较三个模型的得分情况。

让我们使用 Quandl 加载 S&P 500 指数数据,然后准备预测数据。你可以在“多元线性回归”主题的“预测未来”部分中阅读这个过程。

import quandl
import numpy as np
from sklearn import preprocessing
from sklearn import model_selection
from sklearn import linear_model
from sklearn.preprocessing import PolynomialFeatures
from matplotlib import pyplot as plot
from sklearn import svm
data_frame = quandl.get("YALE/SPCOMP")
data_frame[['Long Interest Rate', 'Real Price',
           'Real Dividend', 'Cyclically Adjusted PE Ratio']]
data_frame.fillna(-100, inplace=True)
# We shift the price data to be predicted 20 years forward
data_frame['Real Price Label'] = data_frame['RealPrice'].shift(-240)
# Then exclude the label column from the features
features = np.array(data_frame.drop('Real Price Label', 1))
# We scale before dropping the last 240 rows from the features
scaled_features = preprocessing.scale(features)
# Save the last 240 rows before dropping them
scaled_features_latest240 = scaled_features[-240:]
# Exclude the last 240 rows from the data used for # # modelbuilding
scaled_features = scaled_features[:-240]
# Now we can drop the last 240 rows from the data frame
data_frame.dropna(inplace=True)
# Then build the labels from the remaining data
label = np.array(data_frame['Real Price Label'])
# The rest of the model building stays
(features_train,
    features_test,
    label_train,
    label_test
) = model_selection.train_test_split(
    scaled_features,
    label,
   test_size=0.1
)

让我们先使用一次多项式来评估模型并进行预测。我们仍在重新创建第二个主题中的主要示例。

model = linear_model.LinearRegression()
model.fit(features_train, label_train)
model.score(features_test, label_test)
  1. 输出如下:

     0.8978136465083912
    
  2. 输出始终取决于测试数据,因此每次运行后的值可能不同。

    label_predicted = model.predict(features_test)
    plot.plot(
        label_test, label_predicted, 'o',
        [0, 3000], [0, 3000]
    )
    

图 3.22:显示输出的图表

点越接近 y=x 线,模型的误差就越小。

It is now time to perform a linear multiple regression with quadratic polynomials. The only change is in the Linear Regression model
poly_regressor = PolynomialFeatures(degree=3)
poly_scaled_features = poly_regressor.fit_transform(scaled_features)
(poly_features_train,
 poly_features_test,
 poly_label_train,
 poly_label_test) = model_selection.train_test_split(
    poly_scaled_features,
    label,
    test_size=0.1)
model = linear_model.LinearRegression()
model.fit(poly_features_train, poly_label_train)
print('Polynomial model score: ', model.score(
    poly_features_test, poly_label_test))
print('\n')
poly_label_predicted = model.predict(poly_features_test)
plot.plot(
    poly_label_test, poly_label_predicted, 'o',
    [0, 3000], [0, 3000]
)

模型在测试数据上的表现令人惊讶。因此,我们可以怀疑我们的多项式在训练和测试场景中过度拟合。

我们现在将执行一个具有三次多项式核的支持向量回归。

model = svm.SVR(kernel='poly')
model.fit(features_train, label_train)
label_predicted = model.predict(features_test)
plot.plot(
    label_test, label_predicted, 'o',
    [0,3000], [0,3000]
)

图 3.23:显示输出的图表
model.score(features_test, label_test)

输出将是 0.06388628722032952

我们现在将执行一个具有三次多项式核的支持向量回归。

第四章:分类

活动 7:为分类准备信用数据

本节将讨论如何为分类器准备数据。我们将使用 german.data 从archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/作为示例,并为训练和测试分类器准备数据。确保所有标签都是数值的,并且值已准备好进行分类。使用 80%的数据点作为训练数据。

  1. archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/保存 german.data,并在 Sublime Text 或 Atom 等文本编辑器中打开它。向其中添加以下第一行:

    CheckingAccountStatus DurationMonths CreditHistory CreditPurpose CreditAmount SavingsAccount EmploymentSince DisposableIncomePercent PersonalStatusSex OtherDebtors PresentResidenceMonths Property Age OtherInstallmentPlans Housing NumberOfExistingCreditsInBank Job LiabilityNumberOfPeople Phone ForeignWorker CreditScore
    
  2. 使用 pandas 导入数据文件,并用异常值替换 NA 值:

    import pandas
    data_frame = pandas.read_csv('german.data', sep=' ')
    data_frame.replace('NA', -1000000, inplace=True)
    
  3. 执行标签编码。我们需要将数据框中的所有标签转换为整数。我们可以在一维数组中创建所有标签。然而,这将非常低效,因为每个标签恰好出现在一个列中。按列分组我们的标签更有意义:

    labels = {
     'CheckingAccountStatus': ['A11', 'A12', 'A13', 'A14'],
     'CreditHistory': ['A30', 'A31', 'A32', 'A33', 'A34'],
     'CreditPurpose': ['A40', 'A41', 'A42', 'A43', 'A44', 'A45', 'A46', 'A47', 'A48', 'A49', 'A410'],
     'SavingsAccount': ['A61', 'A62', 'A63', 'A64', 'A65'],
     'EmploymentSince': ['A71', 'A72', 'A73', 'A74', 'A75'],
     'PersonalStatusSex': ['A91', 'A92', 'A93', 'A94', 'A95'],
     'OtherDebtors': ['A101', 'A102', 'A103'],
     'Property': ['A121', 'A122', 'A123', 'A124'],
     'OtherInstallmentPlans': ['A141', 'A142', 'A143'],
     'Housing': ['A151', 'A152', 'A153'],
     'Job': ['A171', 'A172', 'A173', 'A174'],
     'Phone': ['A191', 'A192'],
     'ForeignWorker': ['A201', 'A202']
    }
    
  4. 让我们为每一列创建一个标签编码器并编码值:

    from sklearn import preprocessing
    label_encoders = {}
    data_frame_encoded = pandas.DataFrame()
    for column in data_frame:
        if column in labels:
            label_encoders[column] = preprocessing.LabelEncoder()
            label_encoders[column].fit(labels[column])
            data_frame_encoded[column] = label_encoders[
                column].transform(data_frame[column])
        else:
            data_frame_encoded[column] = data_frame[column]
    

    让我们验证我们是否一切都做对了:

data_frame_encoded.head()
CheckingAccountStatus DurationMonths CreditHistory CreditPurpose \
0                     0             6             4             4
1                     1             48             2             4
2                     3             12             4             7
3                     0             42             2             3
4                     0             24             3             0
   CreditAmount SavingsAccount EmploymentSince DisposableIncomePercent \
0         1169             4                4                        4
1         5951             0                2                        2
2         2096             0                3                        2
3         7882             0                3                        2
4         4870             0                2                        3
   PersonalStatusSex OtherDebtors     ...     Property Age \
0                 2             0     ...             0 67
1                 1             0     ...             0 22
2                 2             0     ...             0 49
3                 2             2     ...             1 45
4                 2             0     ...             3 53
   OtherInstallmentPlans Housing NumberOfExistingCreditsInBank Job \
0                     2        1                             2    2
1                     2        1                             1    2
2                     2        1                             1    1
3                     2        2                             1    2
4                     2        2                             2    2
   LiabilityNumberOfPeople Phone ForeignWorker CreditScore
0                        1     1             0            1
1                        1     0             0            2
2                        2     0             0            1
3                        2     0             0            1
4                        2     0             0            2
[5 rows x 21 columns]
label_encoders
{'CheckingAccountStatus': LabelEncoder(),
 'CreditHistory': LabelEncoder(),
 'CreditPurpose': LabelEncoder(),
 'EmploymentSince': LabelEncoder(),
 'ForeignWorker': LabelEncoder(),
 'Housing': LabelEncoder(),
 'Job': LabelEncoder(),
 'OtherDebtors': LabelEncoder(),
 'OtherInstallmentPlans': LabelEncoder(),
 'PersonalStatusSex': LabelEncoder(),
 'Phone': LabelEncoder(),
 'Property': LabelEncoder(),
 'SavingsAccount': LabelEncoder()}

所有 21 列都可用,标签编码器也已保存到对象中。我们的数据现在已预处理。

如果您不想解码编码后的值,则不需要保存这些标签编码器。我们只是为了完整性而保存了它们。

  1. 是时候将特征与标签分开。我们可以应用我们在理论部分看到的方法:

    import numpy as np
    features = np.array(
        data_frame_encoded.drop(['CreditScore'], 1)
    )
    label = np.array(data_frame_encoded['CreditScore'])
    

    我们的特征尚未缩放。这是一个问题,因为信用金额的距离可能比年龄的差异显著更高。

    我们必须一起对训练数据和测试数据进行缩放,因此,我们可以在将训练数据从测试数据中分割出来之前进行缩放的最新步骤。

  2. 让我们使用 scikit 的预处理库中的 Min-Max 缩放器:

    scaled_features = preprocessing.MinMaxScaler(
    feature_range=(0,1)).fit_transform(features)
    
  3. 最后一步是交叉验证。我们将打乱我们的数据,并使用所有数据的 80%进行训练,20%进行测试。

    from sklearn import model_selection
    features_train, features_test, label_train,
    label_test = model_selection.train_test_split(
        scaled_features,
        label,
        test_size = 0.2
    )
    

活动 8:提高信用评分的准确性

本节将学习 k 最近邻分类器的参数化如何影响最终结果。信用评分的准确性目前相当低:66.5%。找到一种方法将其提高几个百分点。并且为了确保它正确发生,您需要完成之前的练习。

完成这个练习有很多方法。在这个解决方案中,我将向您展示一种通过改变参数化来提高信用评分的方法。

您必须完成第 13 项练习,才能完成此活动。

  1. 将 k 最近邻分类器的 K 值从默认的 5 增加到 10、15、25 和 50。评估结果:

    You must have completed Exercise 13, to be able to complete this activity
    classifier = neighbors.KNeighborsClassifier(n_neighbors=10)
    classifier.fit(
        features_train,label_train
        )
    classifier.score(features_test, label_test)
    
  2. 在为所有四个n_neighbors值运行这些行之后,我得到了以下结果:

    K=10: accuracy is 71.5%
    K=15: accuracy is 70.5%
    K=25: accuracy is 72%
    K=50: accuracy is 74%
    
  3. 较高的 K 值并不一定意味着更好的分数。然而,在这个例子中,K=50K=5得到了更好的结果。

活动 9:在 scikit-learn 中进行支持向量机优化

本节将讨论如何使用支持向量机分类器的不同参数。我们将通过比较和对比你之前学习过的不同支持向量回归分类器参数,找到一组在之前活动中加载和准备好的训练和测试数据上产生最高分类数据的参数。为确保正确执行,你需要完成之前的活动和练习。

我们将尝试几种组合。你可以选择不同的参数,例如

  1. 线性核

    classifier = svm.SVC(kernel="linear")
    classifier.fit(features_train, label_train)
    classifier.score(features_test, label_test)
    
  2. 四次方多项式核,C=2,gamma=0.05

    classifier = svm.SVC(kernel="poly", C=2, degree=4, gamma=0.05)
    classifier.fit(features_train, label_train)
    classifier.score(features_test, label_test)
    

    输出如下:0.705。

  3. 四次方多项式核,C=2,gamma=0.25

    classifier = svm.SVC(kernel="poly", C=2, degree=4, gamma=0.25)
    classifier.fit(features_train, label_train)
    classifier.score(features_test, label_test)
    

    输出如下:0.76。

  4. 四次方多项式核,C=2,gamma=0.5

    classifier = svm.SVC(kernel="poly", C=2, degree=4, gamma=0.5)
    classifier.fit(features_train, label_train)
    classifier.score(features_test, label_test)
    

    输出如下:0.72。

  5. Sigmoid 核

    classifier = svm.SVC(kernel="sigmoid")
    classifier.fit(features_train, label_train)
    classifier.score(features_test, label_test)
    

    输出如下:0.71。

  6. 默认核,gamma 为 0.15

    classifier = svm.SVC(kernel="rbf", gamma=0.15)
    classifier.fit(features_train, label_train)
    classifier.score(features_test, label_test)
    

    输出如下:0.76。

第五章:使用树进行预测分析

活动 10:汽车数据分类

本节将讨论如何构建一个可靠的决策树模型,以帮助你的公司在寻找可能购买汽车的客户方面发挥作用。我们将假设你受雇于一家汽车租赁代理机构,专注于与客户建立长期关系。你的任务是构建一个决策树模型,将汽车分类为以下四个类别之一:不可接受、可接受、好、非常好。

数据集可以通过以下链接访问:archive.ics.uci.edu/ml/datasets/Car+Evaluation。点击数据文件夹链接下载数据集。点击数据集描述链接以访问属性描述。

评估你的决策树模型的效用。

  1. 从这里下载汽车数据文件:archive.ics.uci.edu/ml/machine-learning-databases/car/car.data。在 CSV 文件的前面添加一个标题行,以便在 Python 中更容易引用:

    Buying,Maintenance,Doors,Persons,LuggageBoot,Safety,Class

    我们简单地将标签称为 Class。我们根据archive.ics.uci.edu/ml/machine-learning-databases/car/car.names中的描述命名了六个特征。

  2. 将数据集加载到 Python 中

    import pandas
    data_frame = pandas.read_csv('car.data')
    

    让我们检查数据是否正确加载:

    data_frame.head()
    Buying Maintenance Doors Persons LuggageBoot Safety Class
    0 vhigh     vhigh     2     2     small    low unacc
    1 vhigh     vhigh     2     2     small    med unacc
    2 vhigh     vhigh     2     2     small high unacc
    3 vhigh     vhigh     2     2         med    low unacc
    4 vhigh     vhigh     2     2         med    med unacc
    
  3. 由于分类与数值数据一起工作,我们必须执行标签编码,如前一章所示。

    labels = {
        'Buying': ['vhigh', 'high', 'med', 'low'],
        'Maintenance': ['vhigh', 'high', 'med', 'low'],
        'Doors': ['2', '3', '4', '5more'],
        'Persons': ['2', '4', 'more'],
        'LuggageBoot': ['small', 'med', 'big'],
        'Safety': ['low', 'med', 'high'],
        'Class': ['unacc', 'acc', 'good', 'vgood']
    }
    from sklearn import preprocessing
    label_encoders = {}
    data_frame_encoded = pandas.DataFrame()
    for column in data_frame:
        if column in labels:
            label_encoders[column] = preprocessing.LabelEncoder()
            label_encoders[column].fit(labels[column])
            data_frame_encoded[column] = label_encoders[column].transform(data_frame[column])
        else:
    data_frame_encoded[column] = data_frame[column]
    
  4. 让我们分离特征和标签:

    import numpy as np
    features = np.array(data_frame_encoded.drop(['Class'], 1))
    label = np.array( data_frame_encoded['Class'] )
    
  5. 是时候使用 scikit-learn 的交叉验证(在新版本中为模型选择)功能来分离训练数据和测试数据了。我们将使用 10%的测试数据:

    from sklearn import model_selection
    features_train, features_test, label_train, label_test = model_selection.train_test_split(
        features,
        label,
        test_size=0.1
    )
    

    注意,train_test_split 方法将在 model_selection 模块中可用,而不是从 scikit-learn 0.20 版本开始在 cross_validation 模块中可用。在之前的版本中,model_selection 已经包含了 train_test_split 方法。

  6. 我们已经拥有了构建决策树分类器所需的一切:

    from sklearn.tree import DecisionTreeClassifier
    decision_tree = DecisionTreeClassifier()
    decision_tree.fit(features_train, label_train)
    

    fit 方法的输出如下:

    DecisionTreeClassifier(
        class_weight=None,
        criterion='gini',
        max_depth=None,
        max_features=None,
        max_leaf_nodes=None,
        min_impurity_decrease=0.0,
        min_impurity_split=None,
        min_samples_leaf=1,
        min_samples_split=2,
        min_weight_fraction_leaf=0.0,
        presort=False,
        random_state=None,
        splitter='best'
    )
    

    您可以看到决策树分类器的参数化。我们可以设置很多选项来调整分类器模型的性能。

  7. 让我们根据测试数据对我们的模型进行评分:

    decision_tree.score( features_test, label_test )
    

    输出如下:

     0.9884393063583815
    
  8. 这就是您在第四章之前的知识将如何帮助您进行模型评估的时刻。现在,我们将进一步深入,基于我们在这个主题中学到的 classification_report 特征创建一个更深入的模型评估:

    from sklearn.metrics import classification_report
    print(
        classification_report(
            label_test,
            decision_tree.predict(features_test)
        )
    )
    

    输出如下:

                 precision    recall f1-score support
             0     0.97     0.97     0.97        36
             1     1.00     1.00     1.00         5
             2     1.00     0.99     1.00     127
             3     0.83     1.00     0.91         5
    avg / total     0.99     0.99     0.99     173
    

    该模型已被证明相当准确。在这种情况下,高分可能表明存在过拟合的可能性。

活动 11:为您的租车公司进行随机森林分类

  1. 本节将优化您的分类器,以便在选择未来车队车辆时更好地满足客户需求。我们将对您在本章活动 1 中处理过的汽车经销商数据集执行随机森林和极端随机森林分类。建议进一步改进模型以提高分类器的性能。

    我们可以重用活动 1 的第 1 步至第 5 步。第 5 步的结束看起来如下:

    from sklearn import model_selection
    features_train, features_test, label_train, label_test = model_selection.train_test_split(
        features,
        label,
        test_size=0.1
    )
    

    如果您使用 IPython,您的变量可能已经在您的控制台中可用。

  2. 让我们创建一个随机森林和一个极端随机化树分类器,并训练这些模型。

    from sklearn.ensemble import RandomForestClassifier,ExtraTreesClassifier
    random_forest_classifier = RandomForestClassifier(n_estimators=100, max_depth=6)
    random_forest_classifier.fit(features_train, label_train)
    extra_trees_classifier =ExtraTreesClassifier(
        n_estimators=100, max_depth=6
    )
    extra_trees_classifier.fit(features_train, label_train)
    
  3. 让我们来评估这两个模型在测试数据上的表现:

    from sklearn.metrics import classification_report
    print(
        classification_report(
            label_test,
            random_forest_classifier.predict(features_test)
        )
    )
    

    模型 1 的输出如下:

                     precision    recall f1-score support
             0     0.78     0.78     0.78        36
             1     0.00     0.00     0.00         5
             2     0.94     0.98     0.96     127
             3     0.75     0.60     0.67         5
    avg / total     0.87     0.90     0.89     173
    

    模型 1 的输出如下:

    print(
        classification_report(
            label_test,
            extra_trees_classifier.predict(features_test)
        )
    )
    
                 precision    recall f1-score support
              0     0.72     0.72     0.72        36
              1     0.00     0.00     0.00         5
              2     0.93     1.00     0.96     127
              3     0.00     0.00     0.00         5
    avg / total     0.83     0.88     0.86     173
    
  4. 我们也可以计算准确度分数:

    random_forest_classifier.score(features_test, label_test)
    

    输出如下:

     0.9017341040462428
    

    extraTreesClassifier的输出如下:

    extra_trees_classifier.score(features_test, label_test)
    

    输出如下:

     0.884393063583815
    

    我们可以看到,随机森林分类器在性能上略优于额外树分类器。

  5. 作为一种初步的优化技术,让我们看看哪些特征更重要,哪些特征不太重要。由于随机化,移除不太重要的特征可能会减少模型中的随机噪声。

    random_forest_classifier.feature_importances_
    

    输出如下:

    array([0.12656512, 0.09934031, 0.02073233, 0.35550329, 0.05411809, 0.34374086])
    

    extra_trees_classifier的输出如下:

    extra_trees_classifier.feature_importances_
    

    输出如下:

    array([0.08699494, 0.07557066, 0.01221275, 0.38035005, 0.05879822, 0.38607338])
    

    两个分类器都认为第三个和第五个属性相当不重要。我们可能对第五个属性不太确定,因为两个模型中的重要性分数都超过 5%。然而,我们可以相当确定第三个属性是决策中最不重要的属性。让我们再次查看特征名称。

    data_frame_encoded.head()
    

    输出如下:

    Buying Maintenance Doors Persons LuggageBoot Safety Class
    0     3            3     0        0            2     1    
    1     3            3     0        0            2     2    
    2     3            3     0        0            2     0    
    3     3            3     0        0            1     1    
    4     3            3     0        0            1     2    
    

    最不重要的特征是车门。事后看来很明显:车门数量对汽车评分的影响不如安全评级大。

  6. 从模型中移除第三个特征并重新训练分类器。

    features2 = np.array(data_frame_encoded.drop(['Class', 'Doors'], 1))
    label2 = np.array(data_frame_encoded['Class'])
    features_train2,
    features_test2,
    label_train2,
    label_test2 = model_selection.train_test_split(
        features2,
        label2,
        test_size=0.1
    )
    random_forest_classifier2 = RandomForestClassifier(
        n_estimators=100, max_depth=6
    )
    random_forest_classifier2.fit(features_train2, label_train2)
    extra_trees_classifier2 = ExtraTreesClassifier(
        n_estimators=100, max_depth=6
    )
    extra_trees_classifier2.fit(features_train2, label_train2)
    
  7. 让我们比较新模型与原始模型的表现如何:

    print(
        classification_report(
            label_test2,
            random_forest_classifier2.predict(features_test2)
        )
    )
    

    输出结果如下:

                precision    recall f1-score support
             0     0.89     0.85     0.87        40
             1     0.00     0.00     0.00         3
             2     0.95     0.98     0.96     125
             3     1.00     1.00     1.00         5
    avg / total     0.92     0.93     0.93     173
    
  8. 第二个模型:

    print(
        classification_report(
            label_test2,
            extra_trees_classifier2.predict(features_test2)
        )
    )
    

    输出结果如下:

                precision    recall f1-score support
             0     0.78     0.78     0.78        40
             1     0.00     0.00     0.00         3
             2     0.93     0.98     0.95     125
             3     1.00     0.40     0.57         5
    avg / total     0.88     0.90     0.88     173
    

    虽然我们确实提高了几个百分点,但请注意,由于以下原因,直接的比较是不可能的。首先,训练-测试分割选择了不同的数据用于训练和测试。一些错误选择的数据点可能会轻易导致分数的几个百分点增加或减少。其次,我们训练分类器的方式也有随机元素。这种随机化也可能稍微改变分类器的性能。在解释结果时,始终使用最佳判断,并在必要时在多个训练-测试分割上多次测量你的结果。

  9. 让我们进一步调整分类器的参数。以下参数集将随机森林分类器的 F1 分数提高到 97%:

    random_forest_classifier2 = RandomForestClassifier(
        n_estimators=150,
        max_ depth=8,
        criterion='entropy',
        max_features=5
    )
    random_forest_classifier2.fit(features_train2, label_train2)
    print(
        classification_report(
            label_test2,
            random_forest_classifier2.predict(features_test2)
        )
    )
    

    输出结果如下:

               precision    recall f1-score support
              0     0.95     0.95     0.95        40
              1     0.50     1.00     0.67         3
              2     1.00     0.97     0.98     125
              3     0.83     1.00     0.91         5
    avg / total     0.97     0.97     0.97     173
    
  10. 在 Extra Trees 分类器上使用相同的参数,我们也得到了令人惊讶的好结果:

    extra_trees_classifier2 = ExtraTreesClassifier(
        n_estimators=150,
        max_depth=8,
        criterion='entropy',
        max_features=5
    )
    extra_trees_classifier2.fit(features_train2, label_train2)
    print(
        classification_report(
            label_test2,
            extra_trees_classifier2.predict(features_test2)
        )
    )
    

    输出结果如下:

                precision    recall f1-score support
              0     0.92     0.88     0.90        40
              1     0.40     0.67     0.50         3
              2     0.98     0.97     0.97     125
              3     0.83     1.00     0.91         5
    avg / total     0.95     0.94     0.94     173
    

第六章:聚类

活动 12:销售数据的 k-means 聚类

本节将检测在本质上表现相似的产品销售,以识别产品销售趋势。

我们将使用以下 URL 的 Sales Transactions Weekly 数据集:

archive.ics.uci.edu/ml/datasets/Sales_Transactions_Dataset_Weekly 使用 k-means 算法对数据集进行聚类。确保你根据前几章学到的知识准备你的聚类数据。

使用 k-means 算法的默认设置。

  1. 使用 pandas 加载数据集。

    import pandas
    pandas.read_csv('Sales_Transactions_Dataset_Weekly.csv')
    
  2. 如果你检查 CSV 文件中的数据,你可以意识到第一列包含产品 ID 字符串。这些值只是给聚类过程添加噪声。同时请注意,对于第 0 周到第 51 周,有一个以 W 为前缀的标签和一个归一化标签。使用归一化标签更有意义,因此我们可以从数据集中删除常规的每周标签。

    import numpy as np
    drop_columns = ['Product_Code']
    for w in range(0, 52):
        drop_columns.append('W' + str(w))
    features = data_frame.drop(dropColumns, 1)
    
  3. 我们的数据点已归一化,除了最小值和最大值

    from sklearn.preprocessing import MinMaxScaler
    scaler = MinMaxScaler()
    scaled_features = scaler.fit_transform(features)
    
  4. 创建一个 k-means 聚类模型,并将数据点拟合到 8 个聚类中。

    from sklearn.cluster import KMeans
    k_means_model = KMeans()
    k_means_model.fit(scaled_features)
    
  5. 可以使用 labels_ 属性检索每个数据点的标签。这些标签决定了原始数据框中行的聚类。

    k_means_model.labels_
    
  6. 从聚类算法中检索中心点和标签:

    k_means_model.cluster_centers_
    

    输出结果如下:

     array([5, 5, 4, 5, 5, 3, 4, 5, 5, 5, 5, 5, 4, 5, 0, 0, 0, 0, 0, 4, 4, 4,
           4, 0, 0, 5, 0, 0, 5, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
           0, 0, 0, 0, 0, 5, 0, 0, 5, 0, 0, 0, 0, 0, 4, 0, 0, 5, 0, 0, 5, 0,
           ...
           1, 7, 3, 2, 6, 7, 6, 2, 2, 6, 2, 7, 2, 7, 2, 6, 1, 3, 2, 2, 6, 6,
           7, 7, 7, 1, 1, 2, 1, 2, 7, 7, 6, 2, 7, 6, 6, 6, 1, 6, 1, 6, 7, 7,
           1, 1, 3, 5, 3, 3, 3, 5, 7, 2, 2, 2, 3, 2, 2, 7, 7, 3, 3, 3, 3, 2,
           2, 6, 3, 3, 5, 3, 2, 2, 6, 7, 5, 2, 2, 2, 6, 2, 7, 6, 1])
    

这些标签有什么好处?

假设原始数据框中给出了产品名称。你可以轻松地认识到相似类型的产品销售情况相似。也有一些波动很大的产品,以及具有季节性的产品。例如,如果某些产品推广减肥和塑形,它们往往在一年中的前半段销售,在海滩季节之前。

活动 13:使用 Mean Shift 算法进行形状识别

本节将学习如何对图像进行聚类。我们将假设你正在为一家公司工作,该公司从照片中检测人类情绪。你的任务是提取头像照片中构成面部的像素。

使用均值漂移算法创建一个聚类算法来聚类图像的像素。检查均值漂移算法的结果,并检查在应用于头像图像时,是否有任何聚类包含面部。

然后使用固定默认数量的聚类数:8,应用 k-means 算法。将你的结果与均值漂移聚类算法进行比较。

  1. 选择你想要聚类的图像并加载图像。

  2. 我们从作者的 YouTube 频道选择了这张图片img/Image00076.jpg

    图 7.13:包含作者图片的图像
  3. 图像大小已经显著减小,以便我们的算法能够更快地终止。

    image = Image.open('destructuring.jpg')
    pixels = image.load()
    
  4. 将像素转换成数据框以执行聚类

    import pandas
    data_frame = pandas.DataFrame(
        [[x,y,pixels[x,y][0], pixels[x,y][1], pixels[x,y][2]]
            for x in range(image.size[0])
            for y in range(image.size[1])
        ],
        columns=['x', 'y', 'r', 'g', 'b']
    )
    
  5. 使用 scikit-learn 在图像上执行均值漂移聚类。请注意,这次我们将跳过特征归一化,因为像素的邻近性和颜色成分的邻近性几乎以相同的权重表示。像素距离的最大差异是 750,而颜色成分的最大差异是 256。

    from sklearn.cluster import MeanShift
    mean_shift_model = MeanShift()
    mean_shift_model.fit(data_frame)
    for i in range(len(mean_shift_model.cluster_centers_)):
        image = Image.open('destructuring.jpg')
        pixels = image.load()
        for j in range(len(data_frame)):
            if (mean_shift_model.labels_[j] != i ):
               pixels[ int(data_frame['x'][j]),
           int(data_frame['y'][j]) ] = (255, 255, 255)
        image.save( 'cluster' + str(i) + '.jpg' )
    
  6. 算法找到了以下两个聚类img/Image00077.jpg

    图 7.14:执行 k-means 聚类后的图像
  7. 均值漂移算法将我的皮肤和黄色 JavaScript 以及解构文本处理得足够接近,以至于形成了相同的聚类。

  8. 让我们使用 k-means 算法在相同的数据上形成八个聚类。

    k_means_model = KMeans(n_clusters=8)
    k_means_model.fit(data_frame)
    for i in range(len(k_means_model.cluster_centers_)):
        image = Image.open('destructuring.jpg')
        pixels = image.load()
        for j in range(len(data_frame)):
            if (k_means_model.labels_[j] != i):
                pixels[int(data_frame['x'][j]), int(data_frame['y'][j])] = (255, 255, 255)
        image.save('kmeanscluster' + str(i) + '.jpg')
    
  9. 以下 8 个聚类如下:

    第一个输出的结果如下:

执行 K-means 聚类后的图像

图 7.15:执行 k-means 聚类后的图像

第二个输出的结果如下:

图 7.16:执行 K-means 聚类后的图像

图 7.16:执行 K-means 聚类后的图像

第三个输出的结果如下:

图 7.17:执行 K-means 聚类后的图像

图 7.17:执行 k-means 聚类后的图像

第四个输出的结果如下:

图 7.18:执行 K-means 聚类后的图像

图 7.18:执行 K-means 聚类后的图像

第五个输出的结果如下:

图 7.19:执行 K-means 聚类后的图像

图 7.19:执行 K-means 聚类后的图像

第六个输出的结果如下:

图 7.20:执行 K-means 聚类后的图像

图 7.20:执行 K-means 聚类后的图像

第七个输出的结果如下:

图 7.21:执行 K-means 聚类后的图像

图 7.21:执行 K-means 聚类后的图像

第八个输出的结果如下:

图 7.22:执行 K-means 聚类后的图像

图 7.22:执行 K-means 聚类后的图像

如你所见,第五个簇很好地识别了我的脸。聚类算法确实定位了接近且颜色相似的数据点。

第七章:使用神经网络的深度学习

活动 14:手写数字检测

  1. 本节将讨论如何通过检测手写数字为加密货币交易者提供更多安全性。我们将假设你是新加密货币交易平台的一名软件开发者。你正在实施的最新安全措施需要识别手写数字。使用 MNIST 库训练一个神经网络来识别数字。你可以在 www.tensorflow.org/tutorials/ 上了解更多关于这个数据集的信息。

  2. 尽可能提高模型的准确度。为了确保正确发生,你需要完成前面的主题。

  3. 加载数据集并格式化输入

    import tensorflow.keras.datasets.mnist as mnist
    (features_train, label_train),
    (features_test, label_test) = mnist.load_data()
    features_train = features_train / 255.0
    features_test = features_test / 255.0
    def flatten(matrix):
        return [elem for row in matrix for elem in row]
    features_train_vector = [
        flatten(image) for image in features_train
    ]
    features_test_vector = [
        flatten(image) for image in features_test
    ]
    import numpy as np
    label_train_vector = np.zeros((label_train.size, 10))
    for i, label in enumerate(label_train_vector):
        label[label_train[i]] = 1
    label_test_vector = np.zeros((label_test.size, 10))
    for i, label in enumerate(label_test_vector):
        label[label_test[i]] = 1
    
  4. 设置 Tensorflow 图。现在我们将使用 relu 函数而不是 sigmoid 函数。

    import tensorflow as tf
    f = tf.nn.softmax
    x = tf.placeholder(tf.float32, [None, 28 * 28 ])
    W = tf.Variable( tf.random_normal([784, 10]))
    b = tf.Variable( tf.random_normal([10]))
    y = f(tf.add(tf.matmul( x, W ), b ))
    
  5. 训练模型。

    import random
    y_true = tf.placeholder(tf.float32, [None, 10])
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(
        logits=y,
        labels=y_true
    )
    cost = tf.reduce_mean(cross_entropy)
    optimizer = tf.train.GradientDescentOptimizer(
        learning_rate = 0.5
    ).minimize(cost)
    session = tf.Session()
    session.run(tf.global_variables_initializer())
    iterations = 600
    batch_size = 200
    sample_size = len(features_train_vector)
    for _ in range(iterations):
        indices = random.sample(range(sample_size), batchSize)
        batch_features = [
            features_train_vector[i] for i in indices
        ]
        batch_labels = [
            label_train_vector[i] for i in indices
        ]
        min = i * batch_size
        max = (i+1) * batch_size
        dictionary = {
            x: batch_features,
            y_true: batch_labels
        }
        session.run(optimizer, feed_dict=dictionary)
    
  6. 测试模型

    label_predicted = session.run(classify( x ), feed_dict={
        x: features_test_vector
    })
    label_predicted = [
        np.argmax(label) for label in label_predicted
    ]
    confusion_matrix(label_test, label_predicted)
    

    输出如下:

    array([[ 0, 0, 223, 80, 29, 275, 372, 0, 0, 1],
       [ 0, 915, 4, 10, 1, 13, 192, 0, 0, 0],
       [ 0, 39, 789, 75, 63, 30, 35, 0, 1, 0],
       [ 0, 6, 82, 750, 13, 128, 29, 0, 0, 2],
       [ 0, 43, 16, 16, 793, 63, 49, 0, 2, 0],
       [ 0, 22, 34, 121, 40, 593, 76, 5, 0, 1],
       [ 0, 29, 34, 6, 44, 56, 788, 0, 0, 1],
       [ 1, 54, 44, 123, 715, 66, 24, 1, 0, 0],
       [ 0, 99, 167, 143, 80, 419, 61, 0, 4, 1],
       [ 0, 30, 13, 29, 637, 238, 58, 3, 1, 0]], dtype=int64)
    
  7. 计算准确度分数:

    accuracy_score(label_test, label_predicted)
    

    输出如下:

     0.4633
    
  8. 通过重新运行负责训练数据集的代码段,我们可以提高准确度:

    for _ in range(iterations):
        indices = random.sample(range(sample_size), batch_size)
        batch_features = [
            features_train_vector[i] for i in indices
        ]
        batch_labels = [
            label_train_vector[i] for i in indices
        ]
        min = i * batch_size
        max = (i+1) * batch_size
        dictionary = {
            x: batch_features,
            y_true: batch_labels
        }
        session.run(optimizer, feed_dict=dictionary)
    

    第二次运行:0.5107

    第三次运行:0.5276

    第四次运行:0.5683

    第五次运行:0.6002

    第六次运行:0.6803

    第七次运行:0.6989

    第八次运行:0.7074

    第九次运行:0.713

    第十次运行:0.7163

    第二十次运行:0.7308

    第三十次运行:0.8188

    第四十次运行:0.8256

    第五十次运行:0.8273

    在第五十次运行的末尾,改进后的混淆矩阵如下:

    array([
     [946, 0,    6,    3,    0,    1, 15,    2,    7,    0],
     [ 0,1097,    3,    7,    1,    0,    4,    0, 23,    0],
     [11, 3, 918, 11, 18,    0, 13,    8, 50,    0],
     [3,    0, 23, 925,    2, 10,    4,    9, 34,    0],
     [2,    2,    6,    1, 929,    0, 14,    2, 26,    0],
     [16, 4,    7, 62,    8, 673, 22,    3, 97,    0],
     [8,    2,    4,    3,    8,    8, 912,    2, 11,    0],
     [5,    9, 33,    6,    9,    1,    0, 949, 16,    0],
     [3,    4,    5, 12,    7,    4, 12,    3, 924,    0],
     [8,    5,    7, 40, 470, 11,    5, 212, 251,    0]
    ],
         dtype=int64)
    

结果还不错。超过 8 个数字被准确识别。

活动 15:使用深度学习进行手写数字检测

本节将讨论深度学习如何提高你模型的性能。我们将假设你的老板对你之前活动中展示的结果不满意,要求你考虑在你的原始模型中添加两个隐藏层,并确定新层是否提高了模型的准确度。为了确保正确发生,你需要了解深度学习。

  1. 执行前一个活动的代码并测量模型的准确度。

  2. 通过添加新层来改变神经网络。我们将结合 relusoftmax 激活函数:

    x = tf.placeholder(tf.float32, [None, 28 * 28 ])
    f1 = tf.nn.relu
    W1 = tf.Variable(tf.random_normal([784, 200]))
    b1 = tf.Variable(tf.random_normal([200]))
    layer1_out = f1(tf.add(tf.matmul(x, W1), b1))
    f2 = tf.nn.softmax
    W2 = tf.Variable(tf.random_normal([200, 100]))
    b2 = tf.Variable(tf.random_normal([100]))
    layer2_out = f2(tf.add(tf.matmul(layer1_out, W2), b2))
    f3 = tf.nn.softmax
    W3 = tf.Variable(tf.random_normal([100, 10]))
    b3 = tf.Variable( tf.random_normal([10]))
    y = f3(tf.add(tf.matmul(layer2_out, W3), b3))
    
  3. 重新训练模型

    y_true = tf.placeholder(tf.float32, [None, 10])
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(
        logits=y,
        labels=y_true
    )
    cost = tf.reduce_mean(cross_entropy)
    optimizer = tf.train.GradientDescentOptimizer(
    learning_rate=0.5).minimize(cost)
    session = tf.Session()
    session.run(tf.global_variables_initializer())
    iterations = 600
    batch_size = 200
    sample_size = len(features_train_vector)
    for _ in range(iterations):
        indices = random.sample(range(sample_size), batchSize)
        batch_features = [
            features_train_vector[i] for i in indices
        ]
        batch_labels = [
            label_train_vector[i] for i in indices
        ]
        min = i * batch_size
        max = (i+1) * batch_size
        dictionary = {
            x: batch_features,
            y_true: batch_labels
        }
        session.run(optimizer, feed_dict=dictionary)
    
  4. 评估模型

    label_predicted = session.run(y, feed_dict={
        x: features_test_vector
    })
    label_predicted = [
        np.argmax(label) for label in label_predicted
    ]
    confusion_matrix(label_test, label_predicted)
    

    输出如下:

    array([[ 801, 11,    0, 14,    0,    0, 56,    0, 61, 37],
         [ 2, 1069,    0, 22,    0,    0, 18,    0,    9, 15],
         [ 276, 138,    0, 225,    0,    2, 233,    0, 105, 53],
         [ 32, 32,    0, 794,    0,    0, 57,    0, 28, 67],
         [ 52, 31,    0, 24,    0,    3, 301,    0, 90, 481],
         [ 82, 50,    0, 228,    0,    3, 165,    0, 179, 185],
         [ 71, 23,    0, 14,    0,    0, 712,    0, 67, 71],
         [ 43, 85,    0, 32,    0,    3, 31,    0, 432, 402],
         [ 48, 59,    0, 192,    0,    2, 45,    0, 425, 203],
         [ 45, 15,    0, 34,    0,    2, 39,    0, 162, 712]],
         dtype=int64)
    
  5. 计算准确度分数。

    accuracy_score(label_test, label_predicted)
    

输出为 0.4516

准确度没有提高。

让我们看看进一步的运行是否能提高模型的准确度。

第二次运行:0.5216

第三次运行:0.5418

第四次运行:0.5567

第五次运行:0.564

第六次运行:0.572

第七次运行:0.5723

第八次运行:0.6001

第九次运行:0.6076

第十次运行:0.6834

第二十次运行:0.7439

第三十次运行:0.7496

第四十次运行:0.7518

第五十次运行:0.7536

此后,我们得到了以下结果:0.755,0.7605,0.7598,0.7653

最终的混淆矩阵:

array([[ 954,    0,    2,    1,    0,    6,    8,    0,    5,    4],
     [ 0, 1092,    5,    3,    0,    0,    6,    0, 27,    2],
     [ 8,    3, 941, 16,    0,    2, 13,    0, 35, 14],
     [ 1,    1, 15, 953,    0, 14,    2,    0, 13, 11],
     [ 4,    3,    8,    0,    0,    1, 52,    0, 28, 886],
     [ 8,    1,    5, 36,    0, 777, 16,    0, 31, 18],
     [ 8,    1,    6,    1,    0,    6, 924,    0,    9,    3],
     [ 3, 10, 126, 80,    0,    4,    0,    0, 35, 770],
     [ 4,    0,    6, 10,    0,    6,    4,    0, 926, 18],
     [ 4,    5,    1,    8,    0,    2,    2,    0, 18, 969]],
     dtype=int64)

这个深度神经网络的行为甚至比单层网络更加混沌。它需要经过 600 次迭代,每次迭代 200 个样本,才能将准确率从 0.572 提升到 0.5723。在这个迭代过程不久之后,我们就在同样的迭代次数内将准确率从 0.6076 提升到了 0.6834。