Spark-深度学习秘籍-三-

49 阅读55分钟

Spark 深度学习秘籍(三)

原文:zh.annas-archive.org/md5/D22F0E873CEFD5D61BC00E51F025B8FB

译者:飞龙

协议:CC BY-NC-SA 4.0

第十章:使用深度卷积网络进行人脸识别

在本章中,我们将涵盖以下配方:

  • 下载并将 MIT-CBCL 数据集加载到内存中

  • 从目录中绘制和可视化图像

  • 图像预处理

  • 模型构建、训练和分析

介绍

在当今世界,维护信息安全的需求变得越来越重要,同时也变得越来越困难。有各种方法可以强制执行此安全性(密码、指纹 ID、PIN 码等)。然而,就易用性、准确性和低侵入性而言,人脸识别算法一直表现得非常出色。随着高速计算的可用性和深度卷积网络的发展,进一步增加了这些算法的稳健性成为可能。它们已经变得如此先进,以至于现在它们被用作许多电子设备(例如 iPhoneX)甚至银行应用程序中的主要安全功能。本章的目标是开发一个稳健的、姿势不变的人脸识别算法,用于安全系统。为了本章的目的,我们将使用公开可用的MIT-CBCL数据集,其中包含 10 个不同主题的人脸图像。

下载并将 MIT-CBCL 数据集加载到内存中

在这个配方中,我们将了解如何下载 MIT-CBCL 数据集并将其加载到内存中。

到 2025 年,生物识别行业的预测价值将达到 150 亿美元,这意味着它将前所未有地增长。用于生物识别认证的一些生理特征的例子包括指纹、DNA、面部、视网膜或耳朵特征和声音。虽然 DNA 认证和指纹等技术相当先进,但人脸识别也带来了自己的优势。

由于深度学习模型的最新发展,易用性和稳健性是人脸识别算法如此受欢迎的驱动因素之一。

准备工作

对于这个配方,需要考虑以下关键点:

  • MIT-CBCL数据集由 3,240 张图像组成(每个主题 324 张图像)。在我们的模型中,我们将安排增加数据以增加模型的稳健性。我们将采用诸如移动主题、旋转、缩放和剪切主题等技术来获得这些增强的数据。

  • 我们将使用数据集的 20%(648 张图像)来测试我们的模型,通过从数据集中随机选择这些图像。同样,我们随机选择数据集中 80%的图像,并将其用作我们的训练数据集(2,592 张图像)。

  • 最大的挑战是裁剪图像到完全相同的大小,以便它们可以被馈送到神经网络中。

  • 众所周知,当所有输入图像的大小相同时,设计网络要容易得多。然而,由于这些图像中的一些主题具有侧面轮廓或旋转/倾斜轮廓,我们必须使我们的网络适应接受不同大小的输入图像。

操作方法...

步骤如下。

  1. 通过访问人脸识别主页下载MIT-CBCL数据集,该主页包含用于人脸识别实验的多个数据库。链接以及主页的屏幕截图如下所示:

www.face-rec.org/databases/

  1. 向下导航到名为 MIT-CBCL 人脸识别数据库的链接,并单击它,如下面的屏幕截图所示:

  1. 一旦你点击它,它会带你到一个许可页面,在这个页面上你需要接受许可协议并转到下载页面。一旦在下载页面上,点击立即下载。这将下载一个大约 116MB 的 zip 文件。继续提取内容到工作目录中。

工作原理...

功能如下:

  1. 许可协议要求在任何项目中使用数据库时进行适当引用。该数据库是由麻省理工学院的研究团队开发的。

  2. 特此感谢麻省理工学院和生物计算学习中心提供面部图像数据库。许可证还要求提及题为Component-based Face Recognition with 3D Morphable Models, First IEEE Workshop on Face Processing in Video, Washington, D.C., 2004, B. Weyrauch, J. Huang, B. Heisele, and V. Blanz 的论文。

  3. 以下截图描述了许可协议以及下载数据集的链接:

面部识别数据库主页

  1. 数据集下载并提取后,您将看到一个名为 MIT-CBCL-facerec-database 的文件夹。

  2. 为本章的目的,我们将仅使用**training-synthetic**文件夹中的图像,该文件夹包含所有 3,240 张图像,如下截图所示:

还有更多...

对于本章,您将需要 Python 导入以下库:

  • os

  • matplotlib

  • numpy

  • keras

  • TensorFlow

本章的以下部分将涉及导入必要的库和预处理图像,然后构建神经网络模型并将其加载到其中。

另请参阅

有关本章中使用的软件包的完整信息,请访问以下链接:

绘制和可视化目录中的图像

本节将描述如何在对图像进行预处理并输入到神经网络进行训练之前,如何读取和可视化下载的图像。这是本章中的重要步骤,因为需要可视化图像以更好地了解图像尺寸,以便可以准确裁剪以省略背景并仅保留必要的面部特征。

准备工作

在开始之前,完成导入必要库和函数以及设置工作目录路径的初始设置。

如何做...

步骤如下:

  1. 使用以下代码行下载必要的库。输出必须产生一行,显示Using TensorFlow backend,如下截图所示:
%matplotlib inline
from os import listdir
from os.path import isfile, join
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten, Conv2D
from keras.optimizers import Adam
from keras.layers.normalization import BatchNormalization
from keras.utils import np_utils
from keras.layers import MaxPooling2D
from keras.preprocessing.image import ImageDataGenerator

导入库如下所示:

  1. 按照以下截图中所示的方式打印并设置当前工作目录。在我们的案例中,桌面被设置为工作目录:

  1. 通过使用以下截图中说明的命令直接从文件夹中读取所有图像:

  1. 使用plt.imshow(images[])命令从数据集中打印一些随机图像,如下截图所示,以更好地了解图像中的面部轮廓。这也将给出图像的大小的概念,这将在后期需要:

  1. 这里显示了来自第一张图像的不同测试对象的图像。

工作原理...

功能如下:

  1. mypath变量设置要从中读取所有文件的路径。在此步骤中指定了training-synthetic文件夹,因为本章仅使用该文件夹中的文件。

  2. onlyfiles变量用于通过循环遍历文件夹中包含的所有文件来计算文件夹中的所有文件的数量。这将在下一步中用于读取和存储图像。

  3. images变量用于创建一个大小为 3,240 的空数组,以存储所有尺寸为 200 x 200 像素的图像。

  4. 接下来,通过在 for 循环中使用onlyfiles变量作为参数来循环遍历所有文件,将文件夹中包含的每个图像读取并存储到先前定义的images数组中,使用matplotlib.image函数。

  5. 最后,通过指定不同索引的图像来打印随机选择的图像,您将注意到每个图像都是一个 200 x 200 像素的数组,每个主题可能是面向前方,也可能在两侧之间旋转零至十五度。

还有更多...

以下几点值得注意:

  • 该数据库的一个有趣特点是,每个文件名的第四个数字描述了相应图像中的主题是谁。

  • 图像的名称在某种意义上是唯一的,第四个数字代表了相应图像中的个体。图像名称的两个示例是0001_-4_0_0_60_45_1.pgm0006_-24_0_0_0_75_15_1.pgm。可以很容易地理解,第四个数字分别代表了第二个和第七个个体。

  • 我们需要存储这些信息以备将来在进行预测时使用。这将有助于神经网络在训练过程中了解它正在学习哪个主题的面部特征。

  • 可以通过以下代码将每个图像的文件名读入数组,并使用以下代码将十个主题中的每一个分隔开:

y =np.empty([3240,1],dtype=int)
for x in range(0, len(onlyfiles)):
    if onlyfiles[x][3]=='0': y[x]=0
    elif onlyfiles[x][3]=='1': y[x]=1
    elif onlyfiles[x][3]=='2': y[x]=2
    elif onlyfiles[x][3]=='3': y[x]=3
    elif onlyfiles[x][3]=='4': y[x]=4
    elif onlyfiles[x][3]=='5': y[x]=5
    elif onlyfiles[x][3]=='6': y[x]=6
    elif onlyfiles[x][3]=='7': y[x]=7
    elif onlyfiles[x][3]=='8': y[x]=8
    elif onlyfiles[x][3]=='9': y[x]=9
  • 上述代码将初始化一个大小为 3,240 的空的一维numpy数组(training-synthetic文件夹中的图像数量),并通过循环遍历整个文件集,将相关主题存储在不同的数组中。

  • if语句基本上是在检查每个文件名下的第四个数字,并将该数字存储在初始化的numpy数组中。

  • 在 iPython 笔记本中的输出如下截图所示:

另请参阅

以下博客描述了 Python 中裁剪图像的方法,并可用于图像预处理,这将在下一节中需要:

有关 Adam Optimizer 及其用例的更多信息,请访问以下链接:

图像预处理

在前一节中,您可能已经注意到所有图像都不是脸部正面视图,还有些略微旋转的侧面轮廓。您可能还注意到每个图像中都有一些不必要的背景区域需要去除。本节将描述如何预处理和处理图像,使其准备好被馈送到网络进行训练。

准备工作

考虑以下内容:

  • 许多算法被设计用来裁剪图像的重要部分;例如 SIFT、LBP、Haar-cascade 滤波器等。

  • 然而,我们将用一个非常简单的天真代码来解决这个问题,从图像中裁剪出面部部分。这是该算法的一个新颖之处。

  • 我们发现不必要的背景部分的像素强度为 28。

  • 请记住,每个图像都是一个三通道的 200 x 200 像素矩阵。这意味着每个图像包含三个矩阵或张量,红色、绿色和蓝色像素的强度范围从 0 到 255。

  • 因此,我们将丢弃图像中仅包含像素强度为 28 的行或列。

  • 我们还将确保所有图像在裁剪操作后具有相同的像素大小,以实现卷积神经网络的最高并行性。

操作步骤如下:

步骤如下:

  1. 定义crop()函数以裁剪图像,仅获取重要部分,如下代码所示:
 #function for cropping images to obtain only the significant part
 def crop(img):
      a=28*np.ones(len(img)) 
      b=np.where((img== a).all(axis=1)) 
      img=np.delete(img,(b),0) 
      plt.imshow(img)
      img=img.transpose()
      d=28*np.ones(len(img[0]))
      e=np.where((img== d).all(axis=1))
      img=np.delete(img,e,0) 
      img=img.transpose()
      print(img.shape) 
      super_threshold_indices = img < 29 
      img[super_threshold_indices] = 0
      plt.imshow (img)
      return img[0:150, 0:128]
  1. 使用以下代码循环遍历文件夹中的每个图像并使用前面定义的函数进行裁剪:
#cropping all the images
 image = np.empty([3240,150,128],dtype=int)
 for n in range(0, len(images)):
     image[n]=crop(images[n])
  1. 接下来,随机选择一幅图像并打印它,以检查它是否已从 200 x 200 大小的图像裁剪到不同的大小。在我们的案例中,我们选择了图像 23。可以使用以下代码完成:
 print (image[22])
 print (image[22].shape)
  1. 接下来,使用文件夹中80%的图像作为训练集,剩余的20%作为测试集,将数据分割为测试集和训练集。可以使用以下命令完成:
# Split data into 80/20 split for testing and training
test_ind=np.random.choice(range(3240), 648, replace=False) train_ind=np.delete(range(0,len(onlyfiles)),test_ind)
  1. 一旦数据完成拆分,使用以下命令将训练和测试图像分开:
 # slicing the training and test images 
 y1_train=y[train_ind]
 x_test=image[test_ind]
 y1_test=y[test_ind]
  1. 接下来,将所有裁剪后的图像重塑为 128 x 150 的大小,因为这是要馈送到神经网络中的大小。可以使用以下命令完成:
#reshaping the input images
 x_train = x_train.reshape(x_train.shape[0], 128, 150, 1)
 x_test = x_test.reshape(x_test.shape[0], 128, 150, 1)
  1. 一旦数据完成重塑,将其转换为float32类型,这将使其在下一步中更容易处理。可以使用以下命令从 int 转换为 float32:
 #converting data to float32
 x_train = x_train.astype('float32')
 x_test = x_test.astype('float32')
  1. 在重塑和将数据转换为 float32 类型后,必须对其进行归一化,以调整所有值到相似的范围。这是防止数据冗余的重要步骤。使用以下命令执行归一化:
 #normalizing data
 x_train/=255
 x_test/=255
 #10 digits represent the 10 classes
 number_of_persons = 10
  1. 最后一步是将重塑、归一化的图像转换为向量,因为这是神经网络理解的唯一输入形式。使用以下命令将图像转换为向量:
 #convert data to vectors
 y_train = np_utils.to_categorical(y1_train, number_of_persons)
 y_test = np_utils.to_categorical(y1_test, number_of_persons)

工作原理如下:

功能如下:

  1. crop()函数执行以下任务:

  2. 将所有像素强度为 28 的像素乘以一个 numpy 数组 1,并存储在变量a中。

  3. 检查所有实例,其中整列仅由像素强度为 28 的像素组成,并存储在变量b中。

  4. 删除所有列(或Y轴)中像素强度为 28 的整列。

  5. 绘制生成的图像。

    1. 转置图像,以便对所有行(或X轴)执行类似的操作。
    1. 将所有像素强度为 28 的像素乘以一个numpy数组 1,并存储在变量d中。
  6. 检查所有实例,其中整列仅由像素强度为 28 的像素组成,并存储在变量e中。

  7. 删除所有列(从转置图像中)中像素强度为 28 的整列。

  8. 转置图像以恢复原始图像。

  9. 打印图像的形状。

  10. 在发现像素强度小于 29 的地方,将这些像素强度替换为零,这将导致通过使它们变白来裁剪所有这些像素。

  11. 绘制生成的图像。

  12. 将生成的图像重塑为 150 x 128 像素的大小。

crop()函数的输出,如在 Jupyter 笔记本执行期间所见,如下截图所示:

  1. 接下来,通过循环遍历training-synthetic文件夹中包含的所有文件,将定义的crop()函数应用于所有文件。这将导致如下截图所示的输出:

输出如下:

注意,仅保留了相关的面部特征,并且所有裁剪后的图像的形状都小于 200 x 200,这是初始大小。

  1. 打印任意图像的图像和形状,您会注意到每个图像现在都被调整为一个 150 x 128 像素的数组,并且您将看到以下输出:

  2. 将图像分割为测试集和训练集,并将它们分隔为名为x_trainy1_trainx_testy1_test的变量,将导致以下截图中看到的输出:

  3. 数据的分离如下进行:

  1. 对训练和测试图像进行重塑并将数据类型转换为 float32 将导致以下截图中看到的输出:

还有更多...

考虑以下内容:

  • 一旦图像完成预处理,它们仍然需要被规范化并转换为向量(在本例中是张量),然后才能被输入到网络中。

  • 在最简单的情况下,规范化意味着调整在不同尺度上测量的值到一个概念上的共同尺度,通常是在平均之前。规范化数据总是一个好主意,以防止梯度在梯度下降过程中爆炸或消失,如梯度消失和爆炸问题所示。规范化还确保没有数据冗余。

  • 通过将每个图像中的每个像素除以255来对数据进行规范化,因为像素值的范围在 0 和255之间。这将导致以下截图中看到的输出:

  • 接下来,使用numpy_utils中的to_categorical()函数将图像转换为具有十个不同类的输入向量,如下截图所示:

另请参阅

以下是其他资源:

  • 有关数据规范化的更多信息,请查看以下链接:

www.quora.com/What-is-normalization-in-machine-learning

  • 有关过拟合以及为什么数据被分成测试集和训练集的信息,请访问以下链接:

towardsdatascience.com/train-test-split-and-cross-validation-in-python-80b61beca4b6

  • 有关编码变量及其重要性的更多信息,请访问以下链接:

pbpython.com/categorical-encoding.html

模型构建、训练和分析

我们将使用keras库中的标准顺序模型来构建 CNN。该网络将包括三个卷积层,两个最大池化层和四个全连接层。输入层和随后的隐藏层有 16 个神经元,而最大池化层包含(2,2)的池大小。四个全连接层包括两个密集层和一个扁平层和一个 dropout 层。使用 0.25 的 dropout 来减少过拟合问题。该算法的另一个新颖之处是使用数据增强来对抗过拟合现象。数据增强通过旋转、移位、剪切和缩放图像到不同程度来适应模型。

在输入和隐藏层中,使用relu函数作为激活函数,而在输出层中使用softmax分类器来根据预测的输出对测试图像进行分类。

准备工作

将构建的网络可视化如下图所示:

如何做...

步骤如下:

  1. 使用以下命令在 Keras 框架中使用Sequential()函数定义模型:
model = Sequential()
model.add(Conv2D(16, (3, 3), input_shape=(128,150,1)))  
model.add(Activation('relu')) 
model.add(Conv2D(16, (3, 3))) 
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2))) 
model.add(Conv2D(16,(3, 3))) 
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2))) 
model.add(Flatten()) 

model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.25)) 
model.add(Dense(10))

model.add(Activation('softmax')) 
  1. 打印模型的摘要以更好地了解模型的构建方式,并确保它是根据前述规格构建的。这可以通过使用model.summary()命令来完成。

  2. 接下来,使用以下命令编译模型:

model.compile(loss='categorical_crossentropy', optimizer=Adam(), metrics=        ['accuracy'])
  1. 为了防止过拟合并进一步提高模型的准确性,实现某种形式的数据增强。在这一步中,图像将被剪切、水平和垂直轴上移动、放大和旋转。模型学习和识别这些异常的能力将决定模型的鲁棒性。使用以下命令增强数据:
# data augmentation to minimize overfitting
gen = ImageDataGenerator(rotation_range=8, 
        width_shift_range=0.08, shear_range=0.3,
        height_shift_range=0.08,zoom_range=0.08)
test_gen = ImageDataGenerator()
train_generator = gen.flow(x_train, y_train, batch_size=16) 
test_generator = test_gen.flow(x_test, y_test, batch_size=16)
  1. 最后,使用以下命令进行数据增强后拟合和评估模型:
model.fit_generator(train_generator, epochs=5, validation_data=test_generator)

scores = model.evaluate(x_test, y_test, verbose=0)
print("Recognition Error: %.2f%%" % (100-scores[1]*100))

工作原理...

功能如下:

  1. 通过使用 sequential 函数,定义了一个九层卷积神经网络,每一层执行以下功能:

  2. 第一层是一个具有 16 个神经元的卷积层,并对输入张量/矩阵进行卷积。特征图的大小被定义为一个 3 x 3 的矩阵。由于神经网络需要知道期望的输入类型,因此需要为第一层指定输入形状。由于所有图像都被裁剪为 128 x 150 像素的大小,这也将是网络第一层定义的输入形状。在这一层中使用的激活函数是修正线性单元relu)。

  3. 网络的第二层(第一个隐藏层)是另一个具有 16 个神经元的卷积层。同样,这一层的激活函数将使用relu

  4. 网络的第三层(第二个隐藏层)是一个具有 2 x 2 池大小的最大池化层。这一层的功能是提取通过前两层卷积学习到的所有有效特征,并减小包含所有学习到的特征的矩阵的大小。卷积无非是特征图和输入矩阵(在我们的情况下是图像)之间的矩阵乘法。网络将存储卷积过程中产生的结果值。这些存储的值中的最大值将定义输入图像中的某个特征。这些最大值将由最大池化层保留,该层将省略不相关的特征。

  5. 网络的第四层(第三个隐藏层)是另一个 3 x 3 的特征图的卷积层。在这一层中使用的激活函数将再次是relu函数。

  6. 网络的第五层(第四个隐藏层)是一个具有 2 x 2 池大小的最大池化层。

  7. 网络的第六层(第五个隐藏层)是一个扁平化层,它将包含所有学习到的特征(以数字形式存储)的矩阵转换为单行,而不是多维矩阵。

    1. 网络中的第七层(第六个隐藏层)是一个具有 512 个神经元和relu激活的密集层。每个神经元基本上会处理特定的权重和偏差,这无非是对特定图像中所有学习到的特征的表示。这是为了通过在密集层上使用softmax分类器轻松对图像进行分类。
  8. 网络中的第八层(第七个隐藏层)是一个具有 0.25 或 25%的丢弃概率的丢弃层。这一层将在训练过程中随机丢弃 25%的神经元,并通过鼓励网络使用许多替代路径来防止过拟合。

  9. 网络中的最后一层是一个只有 10 个神经元和softmax分类器的密集层。这是第八个隐藏层,也将作为网络的输出层。

  10. 在定义模型后的输出必须如下截图所示:

  1. 在打印model.summary()函数时,必须看到如下截图中的输出:

  2. 该模型使用分类交叉熵进行编译,这是一个函数,用于在将信息从一个层传输到后续层时测量和计算网络的损失。模型将使用 Keras 框架中的Adam()优化器函数,它基本上会指导网络在学习特征时如何优化权重和偏差。model.compile()函数的输出必须如下截图所示:

  1. 由于神经网络非常密集,总图像数量仅为 3,240,因此我们设计了一种方法来防止过拟合。这是通过执行数据增强从训练集生成更多图像来完成的。在这一步中,图像是通过ImageDataGenerator()函数生成的。该函数通过以下方式对训练和测试集进行图像增强:
  • 旋转它们

  • 剪切它们

  • 移动宽度,基本上是扩大图像

  • 在水平轴上移动图像

  • 在垂直轴上移动图像

前述函数的输出必须如下截图所示:

  1. 最后,模型在训练 5 个时期后适应数据并进行评估。我们获得的输出如下截图所示:

  1. 如您所见,我们获得了 98.46%的准确性,导致错误率为 1.54%。这相当不错,但是卷积网络已经进步了很多,我们可以通过调整一些超参数或使用更深的网络来改进这个错误率。

还有更多...

使用 12 层更深的 CNN(一个额外的卷积和一个额外的最大池化层)将准确性提高到 99.07%,如下截图所示:

在模型构建过程中每两层之后使用数据归一化后,我们进一步将准确性提高到 99.85%,如下截图所示:

您可能会得到不同的结果,但可以随意运行几次训练步骤。以下是您可以采取的一些步骤,以便在将来实验网络以更好地了解它:

  • 尝试更好地调整超参数,并实施更高的丢失百分比,看看网络的响应如何。

  • 当我们尝试使用不同的激活函数或更小(不太密集)的网络时,准确性大大降低。

  • 此外,更改特征图和最大池化层的大小,并查看这如何影响训练时间和模型准确性。

  • 尝试在不太密集的 CNN 中包含更多的神经元并进行调整以提高准确性。这也可能导致更快的网络,训练时间更短。

  • 使用更多的训练数据。探索其他在线存储库,找到更大的数据库来训练网络。当训练数据的大小增加时,卷积神经网络通常表现更好。

另请参见

以下已发表的论文是了解卷积神经网络的更好资源。它们可以作为进一步阅读,以更多地了解卷积神经网络的各种应用:

第十一章:使用 Word2Vec 创建和可视化单词向量

在本章中,我们将涵盖以下内容:

  • 获取数据

  • 导入必要的库

  • 准备数据

  • 构建和训练模型

  • 进一步可视化

  • 进一步分析

介绍

在对文本数据进行神经网络训练并使用 LSTM 单元生成文本之前,重要的是要了解文本数据(如单词、句子、客户评论或故事)在输入神经网络之前是如何转换为单词向量的。本章将描述如何将文本转换为语料库,并从语料库生成单词向量,这样就可以使用欧几里得距离计算或余弦距离计算等技术轻松地对相似单词进行分组。

获取数据

第一步是获取一些要处理的数据。在本章中,我们需要大量的文本数据,将其转换为标记并进行可视化,以了解神经网络如何根据欧几里得距离和余弦距离对单词向量进行排名。这是了解不同单词如何相互关联的重要步骤。反过来,这可以用于设计更好、更高效的语言和文本处理模型。

准备工作

考虑以下内容:

  • 模型的文本数据需要以.txt格式的文件存在,并且您必须确保文件放置在当前工作目录中。文本数据可以是来自 Twitter 动态、新闻动态、客户评论、计算机代码或以.txt格式保存在工作目录中的整本书。在我们的案例中,我们已经将《权力的游戏》书籍作为模型的输入文本。然而,任何文本都可以替换书籍,并且相同的模型也会起作用。

  • 许多经典文本已不再受版权保护。这意味着您可以免费下载这些书籍的所有文本,并将它们用于实验,比如创建生成模型。获取不再受版权保护的免费书籍的最佳途径是 Project Gutenberg(www.gutenberg.org/)。

操作方法...

步骤如下:

  1. 首先访问 Project Gutenberg 网站并浏览您感兴趣的书籍。单击书籍,然后单击 UTF-8,这样您就可以以纯文本格式下载书籍。链接如下截图所示:

Project Gutenberg 数据集下载页面

  1. 点击纯文本 UTF-8 后,您应该会看到一个类似以下截图的页面。右键单击页面,然后单击“另存为...”接下来,将文件重命名为您选择的任何名称,并保存在您的工作目录中:

  1. 现在,您应该在当前工作目录中看到一个带有指定文件名的.txt文件。

  2. Project Gutenberg 为每本书添加了标准的页眉和页脚;这不是原始文本的一部分。在文本编辑器中打开文件,然后删除页眉和页脚。

工作原理...

功能如下:

  1. 使用以下命令检查当前工作目录:pwd

  2. 可以使用cd命令更改工作目录,如下截图所示:

  1. 请注意,在我们的案例中,文本文件包含在名为USF的文件夹中,因此这被设置为工作目录。您可以类似地将一个或多个.txt文件存储在工作目录中,以便作为模型的输入。

  2. UTF-8 指定了文本文件中字符的编码类型。UTF-8代表Unicode 转换格式8表示它使用8 位块来表示一个字符。

  3. UTF-8 是一种折衷的字符编码,可以像 ASCII 一样紧凑(如果文件只是纯英文文本),但也可以包含任何 Unicode 字符(文件大小会略有增加)。

  4. 不需要文本文件以 UTF-8 格式,因为我们将在稍后阶段使用 codecs 库将所有文本编码为 Latin1 编码格式。

还有更多...

有关 UTF-8 和 Latin1 编码格式的更多信息,请访问以下链接:

另请参阅

访问以下链接以更好地了解神经网络中单词向量的需求:

medium.com/deep-math-machine-learning-ai/chapter-9-1-nlp-word-vectors-d51bff9628c1

以下是与将单词转换为向量相关的一些其他有用文章:

monkeylearn.com/blog/word-embeddings-transform-text-numbers/

towardsdatascience.com/word-to-vectors-natural-language-processing-b253dd0b0817

导入必要的库

在开始之前,我们需要导入以下库和依赖项,这些库需要导入到我们的 Python 环境中。这些库将使我们的任务变得更加容易,因为它们具有现成的可用函数和模型,可以代替我们自己进行操作。这也使得代码更加简洁和可读。

做好准备

以下库和依赖项将需要创建单词向量和绘图,并在 2D 空间中可视化 n 维单词向量:

  • 未来

  • codecs

  • glob

  • multiprocessing

  • os

  • pprint

  • re

  • nltk

  • Word2Vec

  • sklearn

  • numpy

  • matplotlib

  • pandas

  • seaborn

如何做...

步骤如下:

  1. 在 Jupyter 笔记本中键入以下命令以导入所有所需的库:
from __future__ import absolute_import, division, print_function
import codecs
import glob
import logging
import multiprocessing
import os
import pprint
import re
import nltk
import gensim.models.word2vec as w2v
import sklearn.manifold
import numpy
as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
%pylab inline
  1. 您应该看到一个类似以下截图的输出:

  1. 接下来,使用以下命令导入stopwordspunkt库:
nltk.download("punkt")
nltk.download("stopwords")
  1. 您看到的输出必须看起来像以下截图:

它是如何工作的...

本节将描述用于此配方的每个库的目的。

  1. future库是 Python 2 和 Python 3 之间的缺失链接。它充当两个版本之间的桥梁,并允许我们使用两个版本的语法。

  2. codecs库将用于对文本文件中所有单词进行编码。这构成了我们的数据集。

  3. Regex 是用于快速查找文件的库。glob函数允许快速高效地在大型数据库中搜索所需的文件。

  4. multiprocessing库允许我们执行并发,这是一种运行多个线程并使每个线程运行不同进程的方式。这是一种通过并行化使程序运行更快的方式。

  5. os库允许与操作系统进行简单交互,如 Mac、Windows 等,并执行诸如读取文件之类的功能。

  6. pprint库提供了一种能够以可用作解释器输入的形式对任意 Python 数据结构进行漂亮打印的功能。

  7. re模块提供了类似于 Perl 中的正则表达式匹配操作。

  8. NLTK 是一个自然语言工具包,能够在非常简短的代码中对单词进行标记。当输入整个句子时,nltk函数会分解句子并输出每个单词的标记。基于这些标记,单词可以被组织成不同的类别。NLTK 通过将每个单词与一个名为词汇表的巨大预训练单词数据库进行比较来实现这一点。

  9. Word2Vec是 Google 的模型,它在一个巨大的单词向量数据集上进行了训练。它将语义上相似的单词归为一类。这将是本节中最重要的库。

  10. sklearn.manifold允许使用t-分布随机邻居嵌入t-SNE)技术对数据集进行降维。由于每个单词向量是多维的,我们需要某种形式的降维技术,将这些单词的维度降低到一个较低的维度空间,以便在 2D 空间中进行可视化。

还有更多...

Numpy是常用的math库。Matplotlib是我们将利用的plotting库,而pandas通过允许轻松重塑、切片、索引、子集和操纵数据,提供了很大的灵活性。

Seaborn库是另一个统计数据可视化库,我们需要与matplotlib一起使用。PunktStopwords是两个数据处理库,简化了诸如将语料库中的文本拆分为标记(即通过标记化)和删除stopwords等任务。

另请参阅

有关使用的一些库的更多信息,请访问以下链接:

准备数据

在将数据馈送到模型之前,需要执行一些数据预处理步骤。本节将描述如何清理数据并准备数据,以便将其馈送到模型中。

准备就绪

首先将所有.txt文件中的文本转换为一个大的语料库。这是通过从每个文件中读取每个句子并将其添加到一个空语料库中来完成的。然后执行一些预处理步骤,以删除诸如空格、拼写错误、stopwords等不规则性。然后必须对清理后的文本数据进行标记化,并通过循环将标记化的句子添加到一个空数组中。

如何做...

步骤如下:

  1. 键入以下命令以在工作目录中搜索.txt文件并打印找到的文件的名称:
book_names = sorted(glob.glob("./*.txt"))
print("Found books:")
book_names

在我们的案例中,工作目录中保存了五本名为got1got2got3got4got5的书籍。

  1. 创建一个corpus,读取每个句子,从第一个文件开始,对其进行编码,并使用以下命令将编码字符添加到corpus中:
corpus = u''
for book_name in book_names:
print("Reading '{0}'...".format(book_name))
with codecs.open(book_name,"r","Latin1") as book_file:
corpus += book_file.read()
print("Corpus is now {0} characters long".format(len(corpus)))
print()
  1. 执行前面步骤中的代码,应该会产生以下截图中的输出:

  1. 使用以下命令从punkt加载英语 pickletokenizer
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
  1. 使用以下命令将整个corpus标记化为句子:
raw_sentences = tokenizer.tokenize(corpus)
  1. 以以下方式定义将句子拆分为其组成单词并删除不必要字符的函数:
def sentence_to_wordlist(raw):
     clean = re.sub("[^a-zA-Z]"," ", raw)
     words = clean.split()
     return words
  1. 将每个句子的每个单词标记化的原始句子全部添加到一个新的句子数组中。使用以下代码完成:
sentences = []
for raw_sentence in raw_sentences:
  if len(raw_sentence) > 0:
  sentences.append(sentence_to_wordlist(raw_sentence))
  1. 从语料库中打印一个随机句子,以直观地查看tokenizer如何拆分句子并从结果创建一个单词列表。使用以下命令完成:
print(raw_sentences[50])
print(sentence_to_wordlist(raw_sentences[50]))
  1. 使用以下命令计算数据集中的总标记数:
token_count = sum([len(sentence) for sentence in sentences])
print("The book corpus contains {0:,} tokens".format(token_count))

工作原理...

执行分词器并对语料库中的所有句子进行分词应该会产生以下截图中的输出:

接下来,删除不必要的字符,如连字符和特殊字符,是以以下方式完成的。使用用户定义的sentence_to_wordlist()函数拆分所有句子会产生以下截图中显示的输出:

将原始句子添加到名为sentences[]的新数组中,将产生如下截图所示的输出:

在打印语料库中的总标记数时,我们注意到整个语料库中有 1,110,288 个标记。这在以下截图中有所说明:

功能如下:

  1. 使用 NLTK 中的预训练tokenizer通过将每个句子计为一个标记来标记整个语料库。每个标记化的句子都被添加到变量raw_sentences中,该变量存储了标记化的句子。

  2. 接下来,常见的停用词被移除,并且通过将每个句子分割成单词来清理文本。

  3. 打印一个随机句子以及其单词列表,以了解其工作原理。在我们的案例中,我们选择打印raw_sentences数组中的第 50 个句子。

  4. 计算并打印句子数组中的总标记数(在我们的案例中是句子)。在我们的案例中,我们看到tokenizer创建了 1,110,288 个标记。

还有...

有关将段落和句子标记化的更多信息,请访问以下链接:

另请参阅

有关正则表达式工作原理的更多信息,请访问以下链接:

stackoverflow.com/questions/13090806/clean-line-of-punctuation-and-split-into-words-python

构建和训练模型

一旦我们将文本数据以数组形式的标记输入到模型中,我们就能够为模型定义一些超参数。本节将描述如何执行以下操作:

  • 声明模型超参数

  • 使用Word2Vec构建模型

  • 在准备好的数据集上训练模型

  • 保存和检查点训练好的模型

准备工作

需要声明的一些模型超参数包括以下内容:

  • 生成的单词向量的维度

  • 最小词数阈值

  • 在训练模型时运行的并行线程数

  • 上下文窗口长度

  • 降采样(对于频繁出现的单词)

  • 设置种子

一旦前面提到的超参数被声明,就可以使用Gensim库中的Word2Vec函数构建模型。

如何做...

步骤如下:

  1. 使用以下命令声明模型的超参数:
num_features = 300
min_word_count = 3
num_workers = multiprocessing.cpu_count()
context_size = 7
downsampling = 1e-3
seed = 1
  1. 使用声明的超参数,使用以下代码行构建模型:
got2vec = w2v.Word2Vec(
    sg=1,
    seed=seed,
    workers=num_workers,
    size=num_features,
    min_count=min_word_count,
    window=context_size,
    sample=downsampling
)
  1. 使用标记化的句子构建模型的词汇表,并通过所有标记进行迭代。这是使用以下方式的build_vocab函数完成的:
got2vec.build_vocab(sentences,progress_per=10000, keep_raw_vocab=False, trim_rule=None)
  1. 使用以下命令训练模型:
got2vec.train(sentences, total_examples=got2vec.corpus_count, total_words=None, epochs=got2vec.iter, start_alpha=None, end_alpha=None, word_count=0, queue_factor=2, report_delay=1.0, compute_loss=False)
  1. 如果尚不存在,请创建一个名为 trained 的目录。使用以下命令保存和检查点trained模型:
if not os.path.exists("trained"):
     os.makedirs("trained")
got2vec.wv.save(os.path.join("trained", "got2vec.w2v"), ignore=[])
  1. 要在任何时候加载保存的模型,请使用以下命令:
got2vec = w2v.KeyedVectors.load(os.path.join("trained", "got2vec.w2v"))

它是如何工作的...

功能如下:

  1. 模型参数的声明不会产生任何输出。它只是在内存中留出空间来存储变量作为模型参数。以下截图描述了这个过程:

  2. 模型是使用前述超参数构建的。在我们的案例中,我们将模型命名为got2vec,但模型可以根据您的喜好进行命名。模型定义如下截图所示:

  3. 在模型上运行build_vocab命令应该会产生如下截图所示的输出:

  4. 通过定义以下截图中所见的参数来训练模型:

  5. 上述命令产生如下截图所示的输出:

  1. 保存、检查点和加载模型的命令产生如下输出,如截图所示:

还有更多...

考虑以下内容:

  • 在我们的案例中,我们注意到build_vocab函数从 1,110,288 个单词的列表中识别出 23,960 个不同的单词类型。然而,对于不同的文本语料库,这个数字会有所不同。

  • 每个单词都由一个 300 维向量表示,因为我们已经声明维度为 300。增加这个数字会增加模型的训练时间,但也会确保模型很容易地泛化到新数据。

  • 发现 1e3 的下采样率是一个很好的比率。这是为了让模型知道何时对频繁出现的单词进行下采样,因为它们在分析时并不重要。这些单词的例子包括 this, that, those, them 等。

  • 设置一个种子以使结果可重现。设置种子也使得调试变得更容易。

  • 由于模型不是很复杂,使用常规 CPU 计算训练模型大约需要 30 秒。

  • 当检查点被检查时,模型被保存在工作目录内的trained文件夹下。

另请参阅

有关Word2Vec模型和 Gensim 库的更多信息,请访问以下链接:radimrehurek.com/gensim/models/word2vec.html

radimrehurek.com/gensim/models/word2vec.html

进一步可视化

本节将描述如何压缩所有训练过的单词的维度,并将其全部放入一个巨大的矩阵以进行可视化。由于每个单词都是一个 300 维的向量,所以需要将其降低到更低的维度,以便我们在 2D 空间中进行可视化。

准备工作

一旦模型在训练后保存和检查点后,开始将其加载到内存中,就像在上一节中所做的那样。在本节中将使用的库和模块有:

  • tSNE

  • pandas

  • Seaborn

  • numpy

如何做...

步骤如下:

  1. 使用以下命令压缩 300 维单词向量的维度:
 tsne = sklearn.manifold.TSNE(n_components=2, random_state=0)
  1. 将所有单词向量放入一个巨大的矩阵(命名为all_word_vectors_matrix),并使用以下命令查看它:
 all_word_vectors_matrix = got2vec.wv.syn0
 print (all_word_vectors_matrix)
  1. 使用以下命令将所有学习到的表示拟合到二维空间中:
 all_word_vectors_matrix_2d =  tsne.fit_transform(all_word_vectors_matrix)
  1. 使用以下代码收集所有单词向量及其相关单词:
 points = pd.DataFrame(
     [
            (word, coords[0], coords[1])
             for word, coords in [
              (word, all_word_vectors_matrix_2d[got2vec.vocab[word].index])
                for word in got2vec.vocab
         ]
    ],
    columns=["word", "x", "y"]
)
  1. 使用以下命令可以获取前十个点的XY坐标以及相关单词:
points.head(10)
  1. 使用以下命令绘制所有点:
sns.set_context("poster")
points.plot.scatter("x", "y", s=10, figsize=(15, 15))
  1. 可以放大绘图图表的选定区域以进行更仔细的检查。通过使用以下函数对原始数据进行切片来实现这一点:
def plot_region(x_bounds, y_bounds):
    slice = points[
        (x_bounds[0] <= points.x) &
        (points.x <= x_bounds[1]) &
        (y_bounds[0] <= points.y) &
        (points.y <= y_bounds[1])
        ]
    ax = slice.plot.scatter("x", "y", s=35, figsize=(10, 8))
        for i, point in slice.iterrows():
            ax.text(point.x + 0.005, point.y + 0.005, point.word,                                                  fontsize=11)
  1. 使用以下命令绘制切片数据。切片数据可以被视为原始所有数据点的放大区域:
plot_region(x_bounds=(20.0, 25.0), y_bounds=(15.5, 20.0))

工作原理...

功能如下:

  1. t-SNE 算法是一种非线性降维技术。计算机在计算过程中很容易解释和处理许多维度。然而,人类一次只能可视化两到三个维度。因此,当试图从数据中得出见解时,这些降维技术非常有用。

  2. 将 300 维向量应用 t-SNE 后,我们能够将其压缩为只有两个维度来绘制和查看。

  3. 通过将 n_components 指定为 2,我们让算法知道它必须将数据压缩到二维空间。完成此操作后,我们将所有压缩后的向量添加到一个名为 all_word_vectors_matrix 的巨大矩阵中,如下图所示:

  4. t-SNE 算法需要对所有这些单词向量进行训练。在常规 CPU 上,训练大约需要五分钟。

  5. 一旦 t-SNE 完成对所有单词向量的训练,它会为每个单词输出 2D 向量。可以通过将它们全部转换为数据框架来将这些向量绘制为点。如下图所示完成此操作:

  6. 我们看到上述代码生成了许多点,其中每个点代表一个单词及其 X 和 Y 坐标。检查数据框架的前二十个点时,我们看到如下图所示的输出:

  1. 通过使用 all_word_vectors_2D 变量绘制所有点,您应该会看到类似以下截图的输出:

  2. 上述命令将生成从整个文本生成的所有标记或单词的绘图,如下图所示:

  3. 我们可以使用 plot_region 函数来放大绘图中的某个区域,以便我们能够实际看到单词及其坐标。这一步骤如下图所示:

  4. 通过设置 x_boundsy_bounds 的值,可以可视化绘图的放大区域,如下图所示:

  5. 可以通过改变 x_boundsy_bounds 的值来可视化相同绘图的不同区域,如下两个截图所示:

另请参阅

还有以下额外的要点:

code.google.com/archive/p/word2vec/

  • 使用以下链接来探索 Seaborn 库的不同功能:

seaborn.pydata.org/

进一步分析

本节将描述可在可视化后对数据执行的进一步分析。例如,探索不同单词向量之间的余弦距离相似性。

准备工作

以下链接是关于余弦距离相似性工作原理的出色博客,并讨论了一些涉及的数学内容:

blog.christianperone.com/2013/09/machine-learning-cosine-similarity-for-vector-space-models-part-iii/

如何做...

考虑以下内容:

  • 可以使用 Word2Vec 的不同功能执行各种自然语言处理任务。其中之一是在给定某个单词时找到最语义相似的单词(即具有高余弦相似性或它们之间的欧几里德距离较短的单词向量)。可以使用 Word2Vecmost_similar 函数来执行此操作,如下图所示:此截图显示了与单词 Lannister 相关的所有最接近的单词:

此截图显示了与单词 Jon 相关的所有单词的列表:

工作原理...

考虑以下内容:

  • 有各种方法来衡量单词之间的语义相似性。我们在本节中使用的方法是基于余弦相似性的。我们还可以通过以下代码来探索单词之间的线性关系:
 def nearest_similarity_cosmul(start1, end1, end2):
    similarities = got2vec.most_similar_cosmul(
        positive=[end2, start1],
        negative=[end1]
)
start2 = similarities[0][0]
print("{start1} is related to {end1}, as {start2} is related to         {end2}".format(**locals()))
return start2
  • 要找到给定一组词的最近词的余弦相似度,请使用以下命令:
nearest_similarity_cosmul("Stark", "Winterfell", "Riverrun")
nearest_similarity_cosmul("Jaime", "sword", "wine")
nearest_similarity_cosmul("Arya", "Nymeria", "dragons")
  • 上述过程如下截图所示:

  • 结果如下:

  • 如本节所示,词向量是所有自然语言处理任务的基础。在深入研究更复杂的自然语言处理模型(如循环神经网络和长短期记忆(LSTM)单元)之前,了解它们以及构建这些模型所涉及的数学是很重要的。

另请参阅

可以进一步阅读有关使用余弦距离相似性、聚类和其他机器学习技术在排名词向量中的应用的内容,以更好地理解。以下是一些有用的关于这个主题的已发表论文的链接:

第十二章:使用 Keras 创建电影推荐引擎

本章将涵盖以下配方:

  • 下载 MovieLens 数据集

  • 操作和合并 MovieLens 数据集

  • 探索 MovieLens 数据集

  • 为深度学习流水线准备数据集

  • 使用 Keras 应用深度学习流水线

  • 评估推荐引擎的准确性

介绍

2006 年,一家小型 DVD 租赁公司着手使他们的推荐引擎提高 10%。那家公司是 Netflix,Netflix 奖值 100 万美元。这场比赛吸引了来自世界各地一些最大科技公司的许多工程师和科学家。获胜参与者的推荐引擎是通过机器学习构建的。Netflix 现在是流媒体数据和向其客户推荐下一步应该观看的内容方面的领先科技巨头之一。

如今,评分随处可见,无论你在做什么。如果你正在寻找去新餐馆吃饭的建议,在线订购服装,观看当地影院的新电影,或者在电视或在线上观看新系列,很可能有一个网站或移动应用会给你一些类型的评分以及对你要购买的产品或服务的反馈。正是因为这种反馈的迅速增加,推荐算法在过去几年变得更加受欢迎。本章将专注于使用深度学习库 Keras 为用户构建电影推荐引擎。

下载 MovieLens 数据集

有一个伟大的研究实验室,它始于 1992 年,位于明尼阿波利斯,明尼苏达州,名为GroupLens,专注于推荐引擎,并且慷慨地从 MovieLens 网站上收集了数百万行数据。我们将使用它的数据集作为我们推荐引擎模型的数据来源。

准备工作

MovieLens 数据集存放在 GroupLens 的以下网站上:

grouplens.org/datasets/movielens/.

重要的是要注意,我们将使用的数据集将直接来自他们的网站,而不是来自第三方中介或存储库。此外,有两个不同的数据集可供我们查询:

  • 推荐用于新研究

  • 推荐用于教育和开发

使用这个数据集的目的纯粹是为了教育目的,因此我们将从网站的教育和开发部分下载数据。教育数据仍然包含大量行数,因为它包含了 10 万个评分,如下截图所示:

此外,该数据集包含了在 1995 年 1 月 9 日至 2015 年 3 月 31 日期间收集的 600 多个匿名用户的信息。该数据集最后更新于 2017 年 10 月。

F Maxwell Harper 和 Joseph A Konstan,2015 年。The MovieLens Datasets: History and Context。ACM 交互智能系统交易 (TiiS) 5, 4, Article 19 (2015 年 12 月),19 页。DOI: dx.doi.org/10.1145/2827872

如何做...

本节将涵盖下载和解压 MovieLens 数据集:

  1. 下载较小的 MovieLens 数据集的研究版本,可在以下网站公开下载:grouplens.org/datasets/movielens/latest/.

  2. 下载名为ml-latest-small.zipZIP文件到我们的一个本地文件夹中,如下截图所示:

  1. 当下载并解压ml-latest-small.zip后,应提取以下四个文件:

  2. links.csv

  3. movies.csv

  4. ratings.csv

  5. tags.csv

  6. 执行以下脚本开始我们的SparkSession

spark = SparkSession.builder \
         .master("local") \
         .appName("RecommendationEngine") \
         .config("spark.executor.memory", "6gb") \
         .getOrCreate()
  1. 通过执行以下脚本确认以下六个文件可供访问:
import os
os.listdir('ml-latest-small/')
  1. 使用以下脚本将每个数据集加载到 Spark 数据框中:
movies = spark.read.format('com.databricks.spark.csv')\
            .options(header='true', inferschema='true')\
            .load('ml-latest-small/movies.csv')
tags = spark.read.format('com.databricks.spark.csv')\
            .options(header='true', inferschema='true')\
            .load('ml-latest-small/tags.csv')
links = spark.read.format('com.databricks.spark.csv')\
            .options(header='true', inferschema='true')\
            .load('ml-latest-small/links.csv')
ratings = spark.read.format('com.databricks.spark.csv')\
            .options(header='true', inferschema='true')\
            .load('ml-latest-small/ratings.csv')
  1. 通过执行以下脚本来确认每个数据集的行数:
print('The number of rows in movies dataset is {}'.format(movies.toPandas().shape[0]))
print('The number of rows in ratings dataset is {}'.format(ratings.toPandas().shape[0]))
print('The number of rows in tags dataset is {}'.format(tags.toPandas().shape[0]))
print('The number of rows in links dataset is {}'.format(links.toPandas().shape[0]))

工作原理...

本节将重点介绍 MovieLens 100K 数据集中每个数据集中的字段。请看以下步骤:

  1. 这些数据集都包含在压缩文件ml-latest-small.zip中,其中ratings.csv数据集将作为我们的数据的伪事实表,因为它包含了每部电影的交易。数据集ratings中有四个列名,如下截图所示:

  1. 该数据集显示了每个用户在其时间内选择的评分,从最早的评分到最新的评分。评分的范围可以从 0.5 到 5.0 星,如下截图中的userId = 1所示:

  1. tags数据集包含一个标签列,其中包含用户用于描述特定电影 ID 的特定单词或短语。如下截图所示,用户 15 对桑德拉·布洛克在她的一部电影中并不特别喜欢:

  1. movies数据集主要是电影类型的查找表。有 19 种唯一的类型可以与电影相关联;但是,重要的是要注意,一部电影可以同时与多种类型相关联,如下截图所示:

  1. 最后一个数据集是links数据集,它也充当查找表。它将 MovieLens 中的电影与流行电影数据库网站(如www.imdb.comwww.themoviedb.org)上可用的数据连接起来。IMDB 的链接在名为 imdbId 的列下,而 MovieDB 的链接在名为 tmdbId 的列下,如下截图所示:

  1. 在完成之前,确认我们确实从所有数据集中获得了预期的行数总是一个好主意。这有助于确保我们在将文件上传到笔记本时没有遇到任何问题。我们应该期望在ratings数据集中看到大约 10 万行,如下截图所示:

还有更多...

虽然我们不会在本章中使用 MovieLens 的 2000 万行数据集版本,但您可以选择在此推荐引擎中使用它。您仍将拥有相同的四个数据集,但ratings数据集的数据量将更大。如果选择这种方法,完整的压缩数据集可以从以下网站下载:

files.grouplens.org/datasets/movielens/ml-latest.zip

另请参阅

要了解本章中使用的 MovieLens 数据集背后的元数据,请访问以下网站:

files.grouplens.org/datasets/movielens/ml-latest-small-README.html

要了解本章中使用的 MovieLens 数据集的历史和背景,请访问以下网站:

www.slideshare.net/maxharp3r/the-movielens-datasets-history-and-context

要了解有关Netflix 奖的更多信息,请访问以下网站:

www.netflixprize.com/

操作和合并 MovieLens 数据集

我们目前有四个不同的数据集,但最终我们希望将其减少到一个数据集。本章将重点介绍如何将我们的数据集减少到一个。

准备工作

本节不需要导入 PySpark 库,但了解 SQL 连接将很有帮助,因为我们将探索多种连接数据框的方法。

如何做...

本节将介绍在 PySpark 中连接数据框的以下步骤:

  1. 执行以下脚本将ratings中的所有字段名重命名,将_1附加到名称的末尾:
for i in ratings.columns:
     ratings = ratings.withColumnRenamed(i, i+'_1') 
  1. 执行以下脚本将movies数据集与ratings数据集进行内连接,创建一个名为temp1的新表:
temp1 = ratings.join(movies, ratings.movieId_1 == movies.movieId, how = 'inner')
  1. 执行以下脚本将temp1数据集与links数据集进行内连接,创建一个名为temp2的新表:
temp2 = temp1.join(links, temp1.movieId_1 == links.movieId, how = 'inner')
  1. 通过左连接temp2tags,创建我们的最终组合数据集mainDF,使用以下脚本:
mainDF = temp2.join(tags, (temp2.userId_1 == tags.userId) & (temp2.movieId_1 == tags.movieId), how = 'left')
  1. 通过执行以下脚本,仅选择我们最终mainDF数据集所需的列:
mainDF = mainDF.select('userId_1',
                       'movieId_1',
                       'rating_1',
                       'title', 
                       'genres', 
                       'imdbId',
                       'tmdbId', 
                       'timestamp_1').distinct()

工作原理...

本节将介绍我们连接表的设计过程,以及将保留哪些最终列:

  1. 如前一节所述,评分数据框将作为我们的事实表,因为它包含每个用户随时间的所有主要评分交易。评分中的列将在与其他三个表的每个后续连接中使用,并且为了保持列的唯一性,我们将在每个列名的末尾附加“_1”,如下图所示:

  1. 现在我们可以将三个查找表与评分表进行连接。前两个与评分的连接是内连接,因为 temp1 和 temp2 的行数仍然是 100,004 行。从 tags 到评分的第三个连接需要是外连接,以避免丢失行。此外,连接需要应用于 movieId 和 userId,因为标签在任何给定时间都是唯一的,对于特定用户和特定电影。三个表 temp1、temp2 和 mainDF 的行数可以在下图中看到:

在处理数据集之间的连接时,通常会遇到三种类型的连接:内连接、左连接和右连接。内连接只在数据集 1 和数据集 2 的连接键都可用时才产生结果集。左连接将产生数据集 1 的所有行,以及数据集 2 中匹配键的行。右连接将产生数据集 2 的所有行,以及数据集 1 中匹配键的行。在本节的后面,我们将探讨 Spark 中的 SQL 连接。

  1. 有趣的是,我们新创建的数据集 mainDF 有 100,441 行,而不是原始评分数据集中的 100,004 行,以及 temp1 和 temp2。有 437 个评分有多个标签与之关联。此外,我们可以看到大多数 ratings_1 都有一个空的 tag 值与之关联,如下图所示:

  1. 我们积累了不再需要的额外重复列。总共有 14 列,如下图所示:

  1. 另外,我们已经确定 tags 字段相对无用,因为有超过 99k 个空值。因此,我们将使用数据框架上的select()函数,只提取我们推荐引擎所需的八列。然后,我们可以确认我们的最终新数据框mainDF有正确的行数,即 100,004 行,如下截图所示:

还有更多...

虽然我们使用 PySpark 在 Spark 数据框架中使用函数进行了连接,但我们也可以通过将数据框注册为临时表,然后使用sqlContext.sql()进行连接:

  1. 首先,我们将使用creatorReplaceTempView()将每个数据集注册为临时视图,如下脚本所示:
movies.createOrReplaceTempView('movies_')
links.createOrReplaceTempView('links_')
ratings.createOrReplaceTempView('ratings_')
  1. 接下来,我们将编写我们的 SQL 脚本,就像我们在任何其他关系数据库中所做的那样,使用sqlContext.sql()函数,如下脚本所示:
mainDF_SQL = \
sqlContext.sql(
"""
    select
    r.userId_1
    ,r.movieId_1
    ,r.rating_1
    ,m.title
    ,m.genres
    ,l.imdbId
    ,l.tmdbId
    ,r.timestamp_1
    from ratings_ r

    inner join movies_ m on 
    r.movieId_1 = m.movieId
    inner join links_ l on 
    r.movieId_1 = l.movieId
"""
)
  1. 最后,我们可以对新数据框 mainDF_SQL 进行分析,并观察它看起来与我们的其他数据框 mainDF 相同,同时保持完全相同的行数,如下图所示:

另请参阅

要了解有关 Spark 中 SQL 编程的更多信息,请访问以下网站:

spark.apache.org/docs/latest/sql-programming-guide.html

探索 MovieLens 数据集

在进行任何建模之前,熟悉源数据集并进行一些探索性数据分析是很重要的。

准备工作

我们将导入以下库来帮助可视化和探索 MovieLens 数据集:matplotlib

如何做...

本节将介绍分析 MovieLens 数据库中电影评分的步骤:

  1. 通过执行以下脚本,检索rating_1列的一些摘要统计:
mainDF.describe('rating_1').show
  1. 通过执行以下脚本构建评分分布的直方图:
import matplotlib.pyplot as plt
%matplotlib inline

mainDF.select('rating_1').toPandas().hist(figsize=(16, 6), grid=True)
plt.title('Histogram of Ratings')
plt.show()
  1. 执行以下脚本以在电子表格数据框中查看直方图的值:
mainDF.groupBy(['rating_1']).agg({'rating_1':'count'})\
 .withColumnRenamed('count(rating_1)', 'Row Count').orderBy(["Row Count"],ascending=False)\
 .show()
  1. 通过执行以下脚本,可以将用户对评分的唯一计数存储为数据框userId_frequency
userId_frequency = mainDF.groupBy(['userId_1']).agg({'rating_1':'count'})\
         .withColumnRenamed('count(rating_1)', '# of Reviews').orderBy(["# of             Reviews"],ascending=False)
  1. 使用以下脚本绘制userID_frequency的直方图:
userId_frequency.select('# of Reviews').toPandas().hist(figsize=(16, 6), grid=True)
plt.title('Histogram of User Ratings')
plt.show()

工作原理...

本节将讨论 MovieLens 数据库中评分和用户活动的分布。请查看以下步骤:

  1. 我们可以看到用户平均电影评分约为 3.5,如下截图所示:

  1. 尽管平均评分为 3.54,但我们可以看到直方图显示中位数评分为 4,这表明用户评分严重偏向较高的评分,如下截图所示:

  1. 直方图背后的数据再次显示,用户最频繁选择 4.0,其次是 3.0,然后是 5.0。此外,有趣的是用户更有可能给出 0.0 级别的评分,而不是 0.5 级别的评分,如下截图所示:

  1. 我们可以查看用户对评分的分布,并且看到一些用户非常活跃地表达对他们所看电影的意见。例如,匿名用户 547 发布了 2391 条评分,如下截图所示:

  1. 然而,当我们查看用户进行评分选择的分布时,我们确实看到,虽然有一些用户单独进行了一千多次选择,但绝大多数用户的选择次数少于 250 次,如下截图所示:

  1. 在上一个截图中直方图的分布呈现长尾格式,表明大多数发生在直方图中心之外。这表明绝大多数评分是由少数用户定义的。

还有更多...

pyspark数据框中有一些类似于pandas数据框的特性,并且可以对特定列执行一些摘要统计。

pandas中,我们使用以下脚本执行摘要统计:dataframe['column'].describe()

pyspark中,我们使用以下脚本执行摘要统计:dataframe.describe('column').show()

另请参阅

要了解 PySpark 中describe()函数的更多信息,请访问以下网站:

spark.apache.org/docs/2.1.0/api/python/pyspark.sql.html#pyspark.sql.DataFrame.describe

为深度学习流水线准备数据集

我们现在准备将我们的数据集准备好,以便输入到我们将在 Keras 中构建的深度学习模型中。

准备工作

在为Keras准备数据集时,我们将在笔记本中导入以下库:

  • import pyspark.sql.functions as F

  • import numpy as np

  • from pyspark.ml.feature import StringIndexer

  • import keras.utils

如何做...

本节将介绍准备数据集用于深度学习流水线的以下步骤:

  1. 执行以下脚本清理列名:
mainDF = mainDF.withColumnRenamed('userId_1', 'userid')
mainDF = mainDF.withColumnRenamed('movieId_1', 'movieid')
mainDF = mainDF.withColumnRenamed('rating_1', 'rating')
mainDF = mainDF.withColumnRenamed('timestamp_1', 'timestamp')
mainDF = mainDF.withColumnRenamed('imdbId', 'imdbid')
mainDF = mainDF.withColumnRenamed('tmdbId', 'tmdbid')
  1. “评分”列目前被分为 0.5 的增量。使用以下脚本调整评分,使其四舍五入为整数:
import pyspark.sql.functions as F
mainDF = mainDF.withColumn("rating", F.round(mainDF["rating"], 0))
  1. 根据“流派”标签的频率,将“流派”列从字符串转换为名为 genreCount 的索引,如下脚本所示:
from pyspark.ml.feature import StringIndexer
string_indexer = StringIndexer(inputCol="genres", outputCol="genreCount")
mainDF = string_indexer.fit(mainDF).transform(mainDF)
  1. 使用以下脚本简化我们的数据框:
mainDF = mainDF.select('rating', 'userid', 'movieid', 'imdbid', 'tmdbid', 'timestamp', 'genreCount')
  1. 使用以下脚本将 mainDF 分割为用于模型训练的训练集和测试集:
trainDF, testDF = mainDF.randomSplit([0.8, 0.2], seed=1234)
  1. 使用以下脚本将我们的两个 Spark 数据框 trainDF 和 testDF 转换为四个 numpy 数组,以便在我们的深度学习模型中使用:
import numpy as np

xtrain_array = np.array(trainDF.select('userid','movieid', 'genreCount').collect())
xtest_array = np.array(testDF.select('userid','movieid', 'genreCount').collect())

ytrain_array = np.array(trainDF.select('rating').collect())
ytest_array = np.array(testDF.select('rating').collect()
  1. 使用以下脚本将 ytrain_array 和 ytest_array 转换为独热编码标签 ytrain_OHE 和 ytest_OHE:
import keras.utils as u
ytrain_OHE = u.to_categorical(ytrain_array)
ytest_OHE = u.to_categorical(ytest_array)

它是如何工作的...

本节将解释我们如何准备数据集用于深度学习流水线:

  1. 在深度学习流水线内使用时,最好在流水线接收数据之前清理列名和列的顺序。重命名列标题后,我们可以查看更新后的列,如下脚本所示:

  1. 对“评分”列进行一些操作,将 0.5 增量的值四舍五入为下一个最接近的整数。这将有助于我们在 Keras 中进行多类分类时,将“评分”分为六个类别,而不是 11 个类别。

  2. 为了在深度学习模型中使用电影流派类型,我们需要将“流派”的字符串值转换为数字标签。最常见的流派类型将获得值 0,下一个最常见的类型将增加值。在以下截图中,我们可以看到 Good Will Hunting 有两种与之关联的流派(Drama | Romance),这是第四种最常见的 genreCount,值为 3.0:

  1. 流派列对于深度模型不再需要,因为它将被 genreCount 列替换,如下截图所示:

  1. 我们的主数据框 mainDF 被分成 trainDF 和 testDF,用于建模、训练和评估,采用 80/20 的分割。所有三个数据框的行数可以在以下截图中看到:

  1. 数据被传递到 Keras 深度学习模型中,使用矩阵而不是数据框。因此,我们的训练和测试数据框被转换为 numpy 数组,并分为 x 和 y。选择用于 xtrain_array 和 xtest_array 的特征是 userid、movieid 和 genreCount。这些是我们用来确定用户可能评分的唯一特征。我们放弃了 imdbid 和 tmdbid,因为它们直接与 movieid 相关,因此不会提供任何额外的价值。时间戳将被删除以过滤掉与投票频率相关的任何偏见。最后,ytest_array 和 ytrain_array 将包含评分的标签值。所有四个数组的形状可以在以下截图中看到:

还有更多...

虽然ytrain_arrayytest_array都是矩阵格式的标签,但它们并不是理想的深度学习编码。由于我们正在构建的是一个分类模型,我们需要以一种能够被模型理解的方式对标签进行编码。这意味着我们的 0 到 5 的评分应该根据它们的值元素被编码为 0 或 1 值。因此,如果一个评分获得了最高值 5,它应该被编码为[0,0,0,0,0,1]。第一个位置保留给 0,第六个位置保留给 1,表示值为 5。我们可以使用keras.utils进行此转换,并将我们的分类变量转换为独热编码变量。通过这样做,我们的训练标签的形状从(80146,1)转换为(80146,6),如下面的屏幕截图所示:

另请参阅

要了解有关keras.utils的更多信息,请访问以下网站:keras.io/utils/

使用 Keras 应用深度学习模型

此时,我们已经准备好将 Keras 应用于我们的数据。

准备工作

我们将使用 Keras 中的以下内容:

  • from keras.models import Sequential

  • from keras.layers import Dense, Activation

如何做...

本节将通过以下步骤介绍如何在数据集上应用 Keras 进行深度学习模型:

  1. keras中导入以下库以构建Sequential模型,使用以下脚本:
from keras.models import Sequential
from keras.layers import Dense, Activation
  1. 使用以下脚本配置keras中的Sequential模型:
model = Sequential()
model.add(Dense(32, activation='relu', input_dim=xtrain_array.shape[1]))
model.add(Dense(10, activation='relu'))
model.add(Dense(ytrain_OHE.shape[1], activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
  1. 我们fit和训练模型,并将结果存储到名为accuracy_history的变量中,使用以下脚本:
accuracy_history = model.fit(xtrain_array, ytrain_OHE, epochs=20, batch_size=32)

工作原理...

本节介绍了应用于数据集的 Keras 模型的配置,以基于所选特征预测评分。

  1. 在 Keras 中,Sequential模型只是层的线性组合,包括以下内容:Dense用于在深度神经网络中定义全连接层的层类型。最后,Activation用于将特征的输入转换为可以用作预测的输出。在神经网络中可以使用许多类型的激活函数;但是,在本章中,我们将使用relusoftmax

  2. Sequential模型配置为包括三个Dense层:

  3. 第一层的input_dim设置为xtrain_array中的特征数量。shape特征拉取值为 3,使用xtrain_array.shape[1]。此外,第一层设置为神经网络的第一层有32个神经元。最后,三个输入参数使用relu激活函数激活。只有第一层需要显式定义输入维度。在后续层中不需要,因为它们将能够从前一层推断出维度的数量。

  4. Sequential模型中的第二层在神经网络中有10个神经元,并且激活函数设置为relu。在神经网络过程中早期使用修正线性单元是有效的,因为它们在训练过程中是有效的。这是因为方程的简单性,任何小于 0 的值都被舍弃,而其他激活函数则不是这样。

  5. Sequential模型的第三层需要根据从 0 到 5 的每种可能的评分情况生成六个输出。这需要将输出设置为ytrain_OHE.shape[1]的值。输出使用softmax函数生成,这在神经网络的末端通常是这样,因为它对分类非常有用。此时,我们正在寻找对 0 到 5 之间的值进行分类。

  6. 一旦层被指定,我们必须compile模型。

  7. 我们使用adam来优化模型,它代表自适应矩估计。优化器非常适合配置模型用于调整和更新神经网络权重的梯度下降的学习率。adam是一种流行的优化器,据说它结合了其他常见优化器的一些最佳特性。

  8. 我们的损失函数设置为categorical_crossentroy,通常用于预测多类分类时使用。损失函数评估模型在训练过程中的性能。

  9. 我们使用训练特征xtrain_array和训练标签ytrain_OHE来训练模型。模型在 20 个时期内进行训练,每次批量大小设置为 32。每个时期的模型输出accuracyloss都被记录在一个名为accuracy_history的变量中,可以在以下截图中查看:

还有更多...

虽然我们可以在每个时期打印出损失和准确度分数,但最好是在每个 20 个时期内可视化这两个输出。我们可以使用以下脚本绘制两者:

plt.plot(accuracy_history.history['acc'])
plt.title('Accuracy vs. Epoch')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.show()

plt.plot(accuracy_history.history['loss'])
plt.title('Loss vs. Epoch')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show()

脚本的输出可以在以下截图中看到:

看起来在第二个时期之后,模型的损失和准确度都稳定下来了。

另请参阅

要了解有关使用keras中的Sequential模型入门的更多信息,请访问以下网站:keras.io/getting-started/sequential-model-guide/

评估推荐引擎的准确性

我们现在可以计算我们基于 Keras 构建的深度学习模型的准确率。

准备工作

评估Sequential模型的准确性需要使用 Keras 中的model.evaluate()函数。

如何做...

我们可以通过执行以下脚本简单地计算准确度分数accuracy_rate

score = model.evaluate(xtest_array, ytest_OHE, batch_size=128)
accuracy_rate = score[1]*100
print('accuracy is {}%'.format(round(accuracy_rate,2)))

它是如何工作的...

我们的模型性能是基于评估我们的测试特征xtest_array和测试标签ytest_OHE。我们可以使用model.evaluate()并将batch_size设置为128来进行评估。我们可以看到我们的准确度约为 39%,如以下截图所示:

这意味着我们能够以近 39%的准确率确定用户对 0 到 5 之间的评分。

另请参阅

要了解有关 Keras 指标的模型性能的更多信息,请访问以下网站:

keras.io/metrics/

第十三章:在 Spark 上使用 TensorFlow 进行图像分类

本章将涵盖以下内容:

  • 下载 30 张梅西和罗纳尔多的图像

  • 配置 PySpark 安装与深度学习包

  • 将图像加载到 PySpark 数据框中

  • 理解迁移学习

  • 创建图像分类训练管道

  • 评估模型性能

  • 微调模型参数

介绍

在过去的几年里,图像识别软件需求不断增加。这种需求与大数据存储的进步不是巧合。Google Photos、Facebook 和 Apple 都利用图像分类软件为用户标记照片。这些公司使用的许多图像识别软件都是基于 TensorFlow 等流行库构建的深度学习模型。本章将通过利用一组图像的训练来学习或识别另一组图像,从而扩展了深度学习的技术。这个概念被称为迁移学习。在本章中,我们将专注于利用迁移学习来识别世界上排名前两位的足球运动员:

  1. 利昂内尔·梅西

  2. 克里斯蒂亚诺·罗纳尔多

看一下这张照片:

下载 30 张梅西和罗纳尔多的图像

在对图像进行任何分类之前,我们必须先从网络上下载我们的足球运动员的图像。

准备工作

浏览器有几个可以批量下载图像的插件。由于 Ubuntu 预装了 Mozilla Firefox 作为浏览器,我们将使用它作为我们首选的浏览器来安装批量图像下载扩展。

如何做...

以下部分解释了如何批量下载图像。看一下这些步骤:

  1. 访问以下网站下载和安装 Firefox 插件:

addons.mozilla.org/en-US/firefox/

  1. 搜索并选择“下载所有图像”插件,如下截图所示:

  1. 这将带我们到安装页面。在此时,选择“添加到 Firefox”,如下截图所示:

  1. 确认您的安装,因为此插件将需要权限访问您的浏览器下载历史记录,访问所有网站的数据,并向您发送通知。

  2. 完成后,您应该在浏览器右上角看到一个小图片图标,用于下载所有图像,如下截图所示:

  1. 现在我们已经准备好开始下载我们的足球运动员的图像,使用新添加的 Firefox 扩展。我们可以访问许多不同的网站来下载图像,例如www.google.com。在本章的目的是,搜索克里斯蒂亚诺·罗纳尔多,并使用www.pexels.com下载他的图像,如下截图所示:

  1. 接下来,点击“下载所有图像”图标,并按照以下截图中显示的设置下载图像:

  1. 点击保存,然后您将有选项将所有图片下载为.zip文件到本地目录。然后您可以将文件解压缩到一个文件夹中,并浏览所有图像。在我们的示例中,所有图像都已提取到/Home/sparkNotebooks/Ch13/football/ronaldo/,如下截图所示:

  1. 在文件夹中的所有图像中,选择 30 张罗纳尔多的图像,并将它们命名为ronaldo1.jpgronaldo2.jpg....ronaldo30.jpg,如下截图所示:

  1. 重复上述步骤,这次为梅西获取 30 张图像。最终的文件夹结构应该如下所示:

工作原理...

本节解释了插件如何批量下载图像到我们想要的位置的过程:

  1. 批量图像下载软件现在已经在浏览器中集成。我们将使用 Firefox 的 Download all Images 附加组件快速下载 Messi 和 Ronaldo 的图片。

  2. 我们希望在应用程序中指定设置以下载质量较低的图片,因此我们设置了最小阈值为 0 字节,最大阈值为 500 字节,图像类型为jpgjpeg

  3. 最后,我们希望精选出最能代表每个球员的 30 张图片,其中 20 张将作为我们的训练数据集,剩下的 10 张将作为我们的测试数据集。其他所有图片都可以删除。

  4. 所有图片都将通过它们的姓氏和 1 到 30 之间的数字进行标记或标签,用于训练目的。例如,Messi1.jpgMessi2.jpgRonaldo1.jpgRonaldo2.jpg等。

还有更多...

虽然您可以使用您自己下载的图片,使用 Download all Images 下载 Ronaldo 和 Messi 的相同图片,这些图片将在本章用于训练目的。

对于 Messi:

github.com/asherif844/ApacheSparkDeepLearningCookbook/tree/master/CH13/football/messi

对于 Ronaldo:

github.com/asherif844/ApacheSparkDeepLearningCookbook/tree/master/CH13/football/ronaldo

另请参阅

其他浏览器也有类似的附加组件和扩展。如果您使用的是 Google Chrome,可以从以下网站下载一个名为Download'em All 的类似附加组件:

chrome.google.com/webstore/detail/downloadem-all/ccdfjnniglfbpaplecpifdiglfmcebce?hl=en-US

配置 PySpark 安装以使用深度学习包

在 PySpark 中有一些额外的配置需要完成,以实现 Databricks 的深度学习包spark-deep-learning。这些配置是在第一章中进行的,为深度学习设置您的 Spark 环境

准备工作

此配置需要在终端中进行更改,使用bash

如何做...

以下部分将逐步介绍如何配置 PySpark 以使用深度学习包:

  1. 打开终端应用程序,输入以下命令:
nano .bashrc.
  1. 滚动到文档底部,查找我们在第一章中创建的sparknotebook()函数。

  2. 更新函数的最后一行。它目前应该看起来像下面这样:

$SPARK_HOME/bin/pyspark.

将其更改为以下内容:

$SPARK_HOME/bin/pyspark --packages databricks:spark-deep-learning:0.1.0-spark2.1-s_2.11.
  1. 一旦配置更改完成,退出文档并执行以下脚本以确认所有必要的更改已保存:
source .bashrc.

它是如何工作的...

以下部分解释了如何修改 PySpark 以整合深度学习包,请查看这些步骤:

  1. 访问 bash 允许我们在命令行上进行配置,如下截图所示:

  1. 在我们的文档末尾,我们可以看到我们的原始函数sparknotebook()仍然完整;但是,我们需要修改它以整合spark-deep-learning包。

  2. 由于这个修改是直接针对 PySpark 的,而不是针对 Python 库的,我们无法使用典型的pip安装将其合并到我们的框架中。相反,我们将修改我们的 PySpark 配置,使其显示如下截图所示:

  1. 我们现在已经配置了我们的 PySpark 安装,以整合包含帮助构建各种解决方案模型的深度学习库。

还有更多...

这个包spark-deep-learningDatabricks管理。 Databricks 是由 Spark 的共同创始人之一 Ali Ghodsi 创立的,并且用于通过统一平台提供托管的 Spark 产品。

另请参阅

要了解为 Spark 开发的其他第三方包,请访问以下网站:

spark-packages.org/

将图像加载到 PySpark 数据框中

现在我们已经准备好开始将图像导入我们的笔记本进行分类。

准备工作

在本节中,我们将使用几个库及其依赖项,这将要求我们在 Ubuntu Desktop 的终端上通过pip install安装以下软件包:

pip install tensorflow==1.4.1
pip install keras==2.1.5
pip install sparkdl
pip install tensorframes
pip install kafka
pip install py4j
pip install tensorflowonspark
pip install jieba

如何做到...

以下步骤将演示如何将图像解码为 Spark 数据框:

  1. 使用以下脚本启动spark会话:
spark = SparkSession.builder \
      .master("local") \
      .appName("ImageClassification") \
      .config("spark.executor.memory", "6gb") \
      .getOrCreate()
  1. 从 PySpark 导入以下库以创建数据框,使用以下脚本:
import pyspark.sql.functions as f
import sparkdl as dl
  1. 执行以下脚本,为 Messi 和 Ronaldo 创建两个数据框,使用每个球员的主文件夹位置:
dfMessi = dl.readImages('football/messi/').withColumn('label', f.lit(0))
dfRonaldo = dl.readImages('football/ronaldo/').withColumn('label',             f.lit(1))
  1. 将每个数据框拆分为66.7/33.3的训练和测试集,并设置随机种子为12,使用以下脚本:
trainDFmessi, testDFmessi = dfMessi.randomSplit([66.7, 33.3], seed = 12)
trainDFronaldo, testDFronaldo = dfRonaldo.randomSplit([66.7, 33.3], seed =     12)
  1. 最后,使用以下脚本将训练数据框和测试数据框合并成一个新的数据框trainDFtestDF
trainDF = trainDFmessi.unionAll(trainDFronaldo)
testDF = testDFmessi.unionAll(testDFronaldo)

它是如何工作的...

以下部分解释了如何加载图像并将其读入 Jupyter 笔记本。看看这些步骤:

  1. 我们总是通过启动 Spark 会话来开始一个 Spark 项目,以设置应用程序名称以及设置 Spark 执行器内存。

  2. 我们导入pyspark.sql.functionssparkdl来帮助基于编码图像构建数据框。当导入sparkdl时,我们看到它在后台使用 TensorFlow,如下图所示:

  1. 使用sparkdl创建数据框,包括三列:文件路径、图像和标签。 Sparkdl 用于导入每个图像并按颜色和形状对其进行编码。此外,使用lit函数将文字值(0 或 1)标记到两个数据框的标签列下,以供训练目的,如下图所示:

  1. 由于每个足球运动员有 30 张图像,因此使用 66.7/33.3 的拆分比例创建 18 张训练图像和 12 张测试图像,如下图所示:

请注意,在使用深度学习时,训练过程中使用的图像越多越好。然而,我们将在本章中尝试证明的一点是,通过将迁移学习作为深度学习的扩展实现,我们可以使用更少的训练样本对图像进行分类,就像本章中 Ronaldo 和 Messi 各只有 30 张图像一样。

  1. 为了构建我们的模型,我们只对创建一个包含 36 张图像的训练数据框感兴趣,以及一个包含剩余 24 张图像的测试数据框。一旦合并数据框,我们可以确认它们的大小是否正确,如下图所示:

还有更多...

在这个过程中可能会丢失,但重要的是要注意,将图像加载到数据框中很容易,只需使用sparkdl.readImages几行代码即可。这展示了使用 Spark 提供的机器学习管道的强大功能。

另请参阅

要了解有关sparkdl包的更多信息,请访问以下存储库:

databricks.github.io/spark-deep-learning/site/api/python/sparkdl.html

理解迁移学习

本章的其余部分将涉及迁移学习技术;因此,我们将在本节中解释迁移学习在我们的架构中的工作原理。

准备工作

本节不需要任何依赖项。

如何做到...

本节将介绍迁移学习的工作步骤:

  1. 确定一个预先训练的模型,将其用作将转移到我们选择的任务的训练方法。在我们的情况下,任务将是识别梅西和罗纳尔多的图像。

  2. 有几种可用的预训练模型可以使用。最受欢迎的是以下几种:

  3. Xception

  4. InceptionV3

  5. ResNet50

  6. VGG16

  7. VGG19

  8. 从预先训练的卷积神经网络中提取并保存一定数量的图像的特征,经过多层的过滤和池化。

  9. 预先训练的卷积神经网络的最后一层被替换为我们要基于数据集分类的特定特征。

工作原理...

本节解释了迁移学习的方法:

  1. 在早期章节中,我们讨论了机器学习模型,尤其是深度学习模型,如何在训练目的上最适合较大的样本。事实上,深度学习的一般座右铭是越多越好。

  2. 然而,有时候高数量的数据或图像并不可用于训练模型。在这种情况下,我们希望将一个领域的学习转移到预测不同领域的结果。已经由开发许多预先训练的模型的机构执行了从卷积神经网络中提取特征和过滤层的繁重工作,例如 InceptionV3 和 ResNet50:

  3. InceptionV3 是在 Google 开发的,比 ResNet50 和 VGG 的权重要小

  4. ResNet50 使用 50 个权重层

  5. VGG16 和 VGG19 分别具有 16 和 19 个权重层

  6. 一些更高级的深度学习库,如 Keras,现在预先构建了这些预先训练的网络,通过指定模型名称来更简化应用。

还有更多...

确定哪个预先训练的模型最适合所涉及的数据或图像集将取决于使用的图像类型。最好尝试不同的预先训练集,并确定哪一个提供了最佳的准确性。

另请参阅

要了解有关 Inception V3 预训练模型的更多信息,请阅读以下论文:

arxiv.org/abs/1409.4842

要了解有关 VGG 预训练模型的更多信息,请阅读以下论文:

arxiv.org/abs/1409.1556

创建图像分类训练管道

我们现在准备构建用于训练数据集的深度学习管道。

准备工作

将导入以下库以帮助管道开发:

  • LogisticRegression

  • Pipeline

如何操作...

以下部分将介绍创建图像分类管道的以下步骤:

  1. 执行以下脚本以开始深度学习管道,并配置分类参数:
from pyspark.ml.classification import LogisticRegression
from pyspark.ml import Pipeline

vectorizer = dl.DeepImageFeaturizer(inputCol="image", 
                           outputCol="features", 
                           modelName="InceptionV3")
logreg = LogisticRegression(maxIter=30, 
         labelCol="label")
pipeline = Pipeline(stages=[vectorizer, logreg])
pipeline_model = pipeline.fit(trainDF)
  1. 创建一个新的数据框predictDF,其中包含原始测试标签以及新的预测分数,使用以下脚本:
predictDF = pipeline_model.transform(testDF)
predictDF.select('prediction', 'label').show(n = testDF.toPandas().shape[0], truncate=False)

工作原理...

以下部分解释了如何配置图像分类管道以实现最佳性能:

  1. LogisticRegression被导入,因为它将是用于区分梅西和罗纳尔多图像的主要分类算法。DeepImageFeaturizersparkdl中导入,以根据图像创建特征,这将作为逻辑回归算法的最终输入。

重要的是要注意,从DeepImageFeaturizer创建的特征将使用基于InceptionV3的预训练模型,并分配一个vectorizer变量。

逻辑回归模型被调整为最多运行 30 次迭代。最后,管道将vectorizerLogisticRegression变量一起输入并将其拟合到训练数据框trainDF中。vectorizer用于从图像中创建数值。DeepImageFeaturizer的输出可以在以下截图中看到:

  1. 测试数据框testDF通过应用拟合的管道模型pipeline_model转换为一个新的数据框predictDF,从而创建一个名为 prediction 的新列。然后我们可以将我们的标签列与我们的预测列进行比较,如下面的屏幕截图所示:

还有更多...

InceptionV3是我们用于分类图像的图像分类器模型;然而,我们也可以很容易地选择其他预训练模型,并在我们的管道中比较准确度。

另请参阅

要了解更多关于迁移学习的信息,请阅读威斯康星大学的以下文章:

ftp.cs.wisc.edu/machine-learning/shavlik-group/torrey.handbook09.pdf

评估模型性能

我们准备好评估我们的模型,并看看我们能多好地区分梅西和罗纳尔多。

准备工作

由于我们将进行一些模型评估,我们需要导入以下库:

  • MulticlassClassificationEvaluator

如何做...

以下部分将介绍以下步骤来评估模型性能:

  1. 执行以下脚本,从predictDF数据框中创建混淆矩阵:
predictDF.crosstab('prediction', 'label').show().
  1. 通过执行以下脚本,基于我们的 24 张罗纳尔多和梅西的测试图像,计算一个准确度得分:
from pyspark.ml.evaluation import MulticlassClassificationEvaluator

scoring = predictDF.select("prediction", "label")
accuracy_score = MulticlassClassificationEvaluator(metricName="accuracy")
rate = accuracy_score.evaluate(scoring)*100
print("accuracy: {}%" .format(round(rate,2))).

它是如何工作的...

以下部分解释了我们如何评估模型性能。看看这些图片:

  1. 我们可以将我们的数据框predictDF转换为交叉表,以创建一个混淆矩阵。这样可以让我们了解我们的模型中有多少真正阳性、假阳性、真正阴性和假阴性,如下面的屏幕截图所示:

  1. 此时,我们准备好计算我们使用 36 张训练图像来准确分类剩下的 24 张罗纳尔多和梅西的测试图像的模型表现如何。从之前的屏幕截图中可以看出,我们有 24 张中的 21 张被准确分类。我们有 2 张梅西的图像被错误分类为罗纳尔多,只有一张罗纳尔多的图像被错误分类为梅西。这应该得出一个准确度得分为 88%。我们可以看到MulticlassClassificationEvaluator的准确度得分也为 87.5%,如下面的屏幕截图所示:

还有更多...

虽然我们最终使用准确度作为衡量模型表现的基准指标,但我们也可以轻松地使用精确度或召回率。此外,我们使用MulticlassClassificationEvaluator来评估模型的准确性。由于在这种情况下我们处理的是二元结果,只有两种类型的罗纳尔多或梅西的图像,我们也可以使用BinaryClassificationEvaluator,如下面的屏幕截图所示:

我们最终的准确率仍然是 87.5%。

另请参阅

要了解有关 PySpark 中逻辑回归函数的MulticlassClassificationEvaluator的更多信息,请访问以下网站:

spark.apache.org/docs/2.2.0/ml-classification-regression.html

微调模型参数

任何模型的准确度都有改进的空间。在本节中,我们将讨论一些可以调整以提高我们模型准确度得分的参数。

准备工作

本节不需要任何新的先决条件。

如何做...

本节将介绍微调模型的步骤。

  1. 使用以下脚本定义一个新的逻辑回归模型,其中包括regParamelasticNetParam的额外参数:
logregFT = LogisticRegression(
 regParam=0.05, 
 elasticNetParam=0.3,
 maxIter=15,labelCol = "label", featuresCol="features")
  1. 使用以下脚本创建一个为新创建的模型配置的新管道:
pipelineFT = Pipeline(stages=[vectorizer, logregFT])
  1. 使用以下脚本将管道拟合到训练数据集trainDF
pipeline_model_FT = pipelineFT.fit(trainDF)
  1. 将模型转换应用于测试数据集testDF,以便使用以下脚本比较实际与预测分数:
predictDF_FT = pipeline_model_FT.transform(testDF)
predictDF_FT.crosstab('prediction', 'label').show()
  1. 最后,使用以下脚本评估新模型的准确率binary_rate_FT
binary_rate_FT = binaryevaluator.evaluate(predictDF_FT)*100
print("accuracy: {}%" .format(round(binary_rate_FT,2)))

工作原理...

本节解释了模型如何进行微调:

  1. 逻辑回归模型logregFT使用regParamelasticNetParam参数进行微调。这两个参数对应于逻辑回归模型的γ和α参数。正则化参数或regParam用于在最小化损失函数和最小化模型过拟合之间找到平衡。我们使模型越复杂,它就越可能过拟合并且不被泛化,但我们也可能会得到更低的训练误差。此外,我们使模型越简单,它就越不容易过拟合,但训练误差可能会更高。

  2. 弹性网参数或elasticNetParam是另一种正则化技术,用于结合多个正则化器 L1 和 L2,以最小化模型的过拟合。此外,我们将迭代次数从 20 降低到 15,以查看是否可以通过包括正则化和减少运行次数同时获得更好的准确度分数。

  3. 再次,就像我们在本章中之前所做的那样,我们创建了一个流水线,其中包括从图像生成的数值特征vectorizer,以及我们的逻辑回归模型logregFT

  4. 然后在训练数据trainDF上拟合模型,并将模型的转换应用于测试数据testDF

  5. 我们可以再次通过交叉表比较模型结果的实际与预测结果,如下截图所示:

  1. 与上一节相比,我们现在只有 1 张错分的图像,而不是 3 张。我们通过将maxIter降低到15次运行,并将regParam设置为0.05elasticNetParam设置为0.3来实现这一点。

  2. 我们的新准确率现在为95.83%,如下截图所示:

还有更多...

当然,我们通过将特定参数纳入我们的模型,将准确率从 87.5%提高到 95.83%。可以进行额外的微调和调整参数,以确定是否可以达到 100%的图像分类模型准确度。

另请参阅

要了解有关逻辑回归中正则化和弹性网参数的更多信息,请访问以下网站:

spark.apache.org/docs/2.2.0/mllib-linear-methods.html#logistic-regression