Spark 深度学习秘籍(三)
原文:
annas-archive.org/md5/0b821d0906a13124b659b6b5635b3940译者:飞龙
第十章:使用深度卷积网络进行面部识别
在本章中,我们将涵盖以下几个教程:
-
下载并将 MIT-CBCL 数据集加载到内存中
-
从目录中绘制和可视化图像
-
图像预处理
-
模型构建、训练和分析
介绍
在当今世界,信息安全的维护变得越来越重要,同时也越来越困难。有多种方法可以加强这种安全性(如密码、指纹识别、PIN 码等)。然而,在使用方便性、准确性和低干扰性方面,面部识别算法表现非常出色。随着高速计算的普及和深度卷积网络的发展,这些算法的鲁棒性得到了进一步提高。它们已经发展得如此先进,以至于现在在许多电子设备(例如 iPhoneX)甚至银行应用中作为主要的安全特性。 本章的目标是开发一个稳健的、对姿势不变的面部识别算法,用于安全系统。在本章中,我们将使用公开提供的MIT-CBCL数据集,其中包含 10 个不同主题的面部图像。
下载并将 MIT-CBCL 数据集加载到内存中
在这个教程中,我们将了解如何下载 MIT-CBCL 数据集并将其加载到内存中。
随着到 2025 年预计价值达到 150 亿美元,生物识别行业正准备迎来前所未有的增长。一些用于生物识别认证的生理特征包括指纹、DNA、面部、视网膜或耳朵特征以及声音。虽然 DNA 认证和指纹技术已经相当先进,但面部识别也带来了其独特的优势。
由于深度学习模型的最新发展,使用便捷性和鲁棒性是面部识别算法如此受欢迎的驱动因素之一。
准备工作
以下关键点需要在此教程中考虑:
-
MIT-CBCL数据集包含 3,240 张图片(每个主题 324 张图片)。在我们的模型中,我们将安排数据增强,以提高模型的鲁棒性。我们将采用诸如平移、旋转、缩放和剪切等技术来获得这些增强数据。 -
我们将使用 20%的数据集来测试我们的模型(648 张图片),通过从数据集中随机选择这些图片。类似地,我们随机选择数据集中 80%的图片,并将其作为我们的训练数据集(2,592 张图片)。
-
最大的挑战是将图像裁剪成完全相同的大小,以便可以输入神经网络。
-
众所周知,当所有输入图像的大小相同时,设计网络要容易得多。然而,由于这些图像中的一些主题有侧面或旋转/倾斜的侧面,我们必须调整我们的网络以接受不同大小的输入图像。
如何实现...
步骤如下。
-
通过访问 FACE RECOGNITION HOMEPAGE 下载
MIT-CBCL数据集,其中包含多个用于人脸识别实验的数据库。以下提供了该主页的链接及截图:
- 导航到名为 MIT-CBCL 人脸识别数据库的链接并点击,如下图所示:
- 一旦点击它,您将进入一个许可页面,您需要接受许可协议并继续前往下载页面。在下载页面,点击
download now,下载一个约 116 MB 的 zip 文件。然后解压该文件到工作目录中。
它是如何工作的...
功能如下:
-
许可协议要求在任何项目中使用该数据库时进行适当引用。该数据库由麻省理工学院的研究团队开发。
-
特此感谢麻省理工学院以及生物与计算学习中心提供的人脸图像数据库。许可协议还要求提及论文标题 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, 和 V. Blanz。
-
以下截图描述了许可协议以及下载数据集的链接:
人脸识别数据库主页
-
下载并解压数据集后,您将看到一个名为 MIT-CBCL-facerec-database 的文件夹。
-
对于本章内容,我们只使用
training-synthetic文件夹中的图像,该文件夹包含所有 3,240 张图像,如下图所示:
还有更多内容...
本章内容中,您需要通过 Python 导入以下库:
-
os -
matplotlib -
numpy -
keras -
TensorFlow
本章的以下部分将处理导入必要的库以及在构建神经网络模型并将其加载之前对图像进行预处理。
另见
有关本章中使用的包的完整信息,请访问以下链接:
从目录中绘制和可视化图像
本节将描述如何在图像被预处理并输入神经网络进行训练之前,读取和可视化下载的图像。这是本章的重要步骤,因为需要可视化图像,以便更好地理解图像的大小,从而准确裁剪去除背景,仅保留必要的面部特征。
准备工作
在开始之前,完成导入必要库和函数的初始设置,并设置工作目录的路径。
如何操作...
步骤如下:
- 使用以下代码行下载所需的库。输出应该显示一行
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
库的导入如图所示:
- 打印并设置当前工作目录,如下图所示。在我们的例子中,桌面被设置为工作目录:
- 使用下图所示的命令,直接从文件夹中读取所有图像:
- 使用
plt.imshow(images[])命令打印数据集中的一些随机图像,如下图所示,以便更好地了解图像中的面部轮廓。这也有助于了解图像的大小,后续步骤中需要用到:
- 这里展示的是来自第一张图片的不同测试对象的图像。
工作原理...
功能如下:
-
mypath变量设置了读取所有文件的路径。在这一步中,指定了training-synthetic文件夹,因为本章将只使用该文件夹中的文件。 -
onlyfiles变量用于计算在前一步提供的文件夹路径下的所有文件,通过遍历文件夹中的所有文件。这将在下一步中用于读取和存储图像。 -
images变量用于创建一个大小为 3,240 的空数组,用于存储图像,这些图像的大小为 200 x 200 像素。 -
接下来,通过在 for 循环中使用
onlyfiles变量作为参数遍历所有文件,读取文件夹中包含的每个图像,并使用matplotlib.image函数将其存储到之前定义的images数组中。 -
最后,通过指定不同的图像索引打印随机选择的图像时,您会注意到每张图像是一个 200 x 200 像素的数组,每个主体可能正面朝向,或者在左右两侧之间旋转零至十五度。
还有更多内容...
以下几点需要注意:
-
这个数据库的一个有趣特征是,每个文件名的第四个数字描述了该图像中的主体。
-
图像的名称是唯一的,第四个数字表示该图像中的个人。两个图像名称的例子是
0001_-4_0_0_60_45_1.pgm和0006_-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 优化器及其使用案例的更多信息,可以通过访问以下链接找到:
图像预处理
在上一节中,您可能已经注意到,并非所有图像都是面部的正面视图,还有些略微旋转的侧面轮廓。您可能还注意到每张图像中有些不必要的背景区域需要去除。本节将描述如何预处理和处理图像,使其准备好输入到网络中进行训练。
准备就绪
请考虑以下内容:
-
有许多算法被设计用来裁剪图像的显著部分;例如,SIFT、LBP、Haar-cascade 滤波器等等。
-
然而,我们将通过一个非常简单的朴素代码来解决这个问题,从图像中裁剪出面部部分。这是这个算法的一大创新。
-
我们发现不必要的背景部分的像素强度为 28。
-
记住,每个图像都是一个 200 x 200 像素的三通道矩阵。这意味着每个图像包含三个矩阵或张量,分别代表红色、绿色和蓝色的像素,强度范围从 0 到 255。
-
因此,我们将丢弃任何包含仅为 28 的像素强度的图像的行或列。
-
我们还将确保在裁剪操作后,所有图像都具有相同的像素大小,以实现卷积神经网络的最高并行化能力。
如何实现...
步骤如下:
- 定义
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]
- 使用以下代码行循环遍历文件夹中的每个图像,并使用前面定义的函数裁剪它:
#cropping all the images
image = np.empty([3240,150,128],dtype=int)
for n in range(0, len(images)):
image[n]=crop(images[n])
- 接下来,随机选择一张图像并打印出来,检查它是否已经从 200 x 200 大小的图像裁剪为不同的尺寸。我们在这个案例中选择了图像 23。可以使用以下代码行完成此操作:
print (image[22])
print (image[22].shape)
- 接下来,将数据拆分为测试集和训练集,使用文件夹中的
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)
- 一旦数据完成拆分,使用以下命令将训练图像和测试图像分开:
# slicing the training and test images
y1_train=y[train_ind]
x_test=image[test_ind]
y1_test=y[test_ind]
- 接下来,将所有裁剪后的图像调整为 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)
- 一旦数据完成调整形状,将其转换为
float32类型,这将使得在下一个步骤进行归一化时更容易处理。从 int 类型转换为 float32 类型可以使用以下命令:
#converting data to float32
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
- 在重新调整形状并将数据转换为 float32 类型后,必须对其进行归一化,以便将所有值调整到相似的尺度。这是防止数据冗余的一个重要步骤。使用以下命令进行归一化:
#normalizing data
x_train/=255
x_test/=255
#10 digits represent the 10 classes
number_of_persons = 10
- 最后一步是将调整形状并归一化后的图像转换为向量,因为这是神经网络能够理解的唯一输入形式。使用以下命令将图像转换为向量:
#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)
它是如何工作的...
功能如下:
-
crop()函数执行以下任务:-
将所有强度为 28 的像素与一个全是 1 的 numpy 数组相乘,并存储在变量
a中。 -
检查所有完全由像素强度为 28 的列组成的实例,并将其存储在变量
b中。 -
删除所有列(或Y轴),如果整个列的像素强度都是 28。
-
绘制结果图像。
-
-
- 转置图像,以便在所有行(或X轴)上执行前述一系列操作。
-
-
将所有像素强度为 28 的像素与 1 的
numpy数组相乘,并将其存储在变量d中。 -
检查所有实例,其中整列仅由像素强度为 28 的像素组成,并将其存储在变量
e中。 -
删除所有列(来自转置图像),其中整列的像素强度为 28。
-
转置图像,以恢复原始图像。
-
打印图像的形状。
-
每当发现像素强度小于 29 时,将这些像素的强度替换为零,这将通过将它们变为白色来裁剪所有这些像素。
-
绘制结果图像。
-
将结果图像的尺寸调整为 150 x 128 像素。
-
crop()函数的输出,如在 Jupyter Notebook 执行期间所见,展示在以下截图中:
- 接下来,定义的
crop()函数将应用于training-synthetic文件夹中包含的所有文件,通过遍历每个文件。这将导致如下所示的输出:
输出继续如下:
请注意,只有相关的面部特征被保留下来,所有裁剪后的图像的尺寸都小于 200 x 200,这是最初的尺寸。
-
在打印任意图像及其形状时,你会注意到每个图像现在都已调整为 150 x 128 像素的数组,你将看到以下输出:
-
将图像拆分为测试集和训练集,并将其划分为命名为
x_train、y1_train、x_test和y1_test的变量,将得到以下截图所示的输出: -
数据的分隔如下进行:
- 重新调整训练和测试图像的形状,并将数据类型转换为 float32,结果如以下截图所示:
还有更多内容...
请考虑以下内容:
-
一旦图像完成预处理,它们仍然需要标准化并转换为向量(在这种情况下是张量),然后才能输入到网络中。
-
标准化,在最简单的情况下,意味着将不同尺度上测量的值调整为一个公认的共同尺度,通常是在平均化之前。标准化数据始终是个好主意,因为它可以防止在梯度下降过程中出现梯度爆炸或消失问题,如梯度消失和爆炸问题所示。标准化还确保没有数据冗余。
-
数据归一化是通过将每张图像中的每个像素值除以
255来完成的,因为像素值范围是从 0 到255。这将产生如下所示的输出: -
接下来,使用来自
numpy_utils的to_categorical()函数,将图像转换为具有十个不同类别的输入向量,如下图所示:
另见
其他资源如下:
-
有关数据归一化的更多信息,请查看以下链接:
-
有关过拟合以及为什么数据需要分成测试集和训练集的更多信息,请访问以下链接:
towardsdatascience.com/train-test-split-and-cross-validation-in-python-80b61beca4b6 -
有关编码变量及其重要性的更多信息,请访问以下链接:
模型构建、训练和分析
我们将使用keras库中的标准顺序模型来构建 CNN。该网络将包括三层卷积层、两层最大池化层和四层全连接层。输入层和后续的隐藏层有 16 个神经元,而最大池化层的池大小为(2,2)。四个全连接层由两层密集层、一层展平层和一层丢弃层组成。使用丢弃率 0.25 来减少过拟合问题。这个算法的另一个创新之处在于使用数据增强来对抗过拟合现象。数据增强通过旋转、平移、剪切和缩放图像到不同程度来适应模型。
relu函数作为输入层和隐藏层的激活函数,而softmax分类器则用于输出层,以根据预测输出对测试图像进行分类。
准备开始
将要构建的网络可以通过下图进行可视化:
如何做...
步骤如下:
- 使用以下命令,在 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'))
-
打印模型的摘要,以便更好地理解模型的构建方式,并确保它按照前面的规格构建。这可以通过使用
model.summary()命令来完成。 -
接下来,使用以下命令编译模型:
model.compile(loss='categorical_crossentropy', optimizer=Adam(), metrics= ['accuracy'])
- 为了防止过拟合并进一步提高模型的准确性,实现某种形式的数据增强。在此步骤中,图像将进行剪切、水平和垂直轴上的平移、缩放和旋转。模型学习并识别这些异常的能力将决定模型的鲁棒性。使用以下命令对数据进行增强:
# 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)
- 最后,使用以下命令在数据增强后拟合和评估模型:
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))
它是如何工作的...
功能如下:
-
通过使用顺序函数,定义了一个九层的卷积神经网络,每一层执行以下功能:
-
第一层是一个卷积层,具有 16 个神经元,并对输入张量/矩阵进行卷积。特征图的大小定义为 3 x 3 的矩阵。需要为第一层指定输入形状,因为神经网络需要知道期望的输入类型。由于所有图像已被裁剪为 128 x 150 像素的大小,因此这也将是定义第一层输入形状的标准。该层使用的激活函数是修正线性单元(relu)。
-
网络的第二层(第一个隐藏层)是另一个卷积层,同样包含 16 个神经元。此层的激活函数也将使用
relu。 -
网络的第三层(第二个隐藏层)是一个最大池化层,池化大小为 2 x 2。该层的功能是提取通过前两层卷积学习到的所有有效特征,并减少包含所有学习到的特征的矩阵的大小。卷积不过是特征图和输入矩阵之间的矩阵乘法(在我们的例子中是图像)。形成卷积过程的结果值会被网络存储在矩阵中。这些存储值中的最大值将定义输入图像中的某个特征。最大池化层将保留这些最大值,并丢弃与之无关的特征。
-
网络的第四层(第三个隐藏层)是另一个卷积层,特征图大小再次为 3 x 3。该层使用的激活函数仍然是
relu函数。 -
网络的第五层(第四个隐藏层)是一个最大池化层,池化大小为 2 x 2。
-
网络的第六层(第五个隐藏层)是一个展平层,将包含所有学习到的特征(以数字形式存储)的矩阵转换为单行,而不是多维矩阵。
-
-
-
网络中的第七层(第六个隐藏层)是一个具有 512 个神经元的全连接层,并使用
relu激活函数。每个神经元基本上会处理某个权重和偏置,这只是对特定图像所学习到的所有特征的表示。这样做是为了通过在全连接层上使用softmax分类器,轻松对图像进行分类。 -
网络中的第八层(第七个隐藏层)是一个丢弃层,丢弃概率为 0.25 或 25%。该层将在训练过程中随机
dropout25%的神经元,并通过鼓励网络使用多条替代路径来学习给定特征,从而帮助防止过拟合。 -
网络中的最终层是一个密集层,只有 10 个神经元和
softmax分类器。这是第八个隐藏层,也将作为网络的输出层。
-
-
定义模型后的输出应该类似以下截图:
-
打印
model.summary()函数时,您必须看到类似以下截图的输出: -
该模型使用类别交叉熵(categorical crossentropy)进行编译,这是一个衡量和计算网络损失的函数,用于在层与层之间传递信息。模型将使用 Keras 框架中的
Adam()优化器函数,该函数将基本决定网络在学习特征时如何优化权重和偏差。model.compile()函数的输出应该类似以下截图:
-
由于神经网络相当密集,并且总图像数量仅为 3,240,我们设计了一种方法来防止过拟合。通过执行数据增强,从训练集生成更多图像来实现这一点。在此步骤中,图像通过
ImageDataGenerator()函数生成。此函数将训练集和测试集作为输入,通过以下方式增强图像:-
旋转它们
-
剪切它们
-
水平平移图像,实际上就是扩宽图像
-
在水平方向上平移图像
-
在垂直方向上平移图像
-
前面的函数的输出应该类似以下截图:
- 最后,模型在训练 5 个 epoch 后被拟合到数据并评估。我们得到的输出如以下截图所示:
- 如您所见,我们获得了 98.46%的准确率,导致了 1.54%的误差率。这个结果相当不错,但卷积网络已经取得了很大进展,我们可以通过调优一些超参数或使用更深的网络来改善这个误差率。
还有更多...
使用更深的 CNN,增加了 12 层(多了一层卷积层和一层最大池化层),将准确率提升至 99.07%,如以下截图所示:
在每两层后使用数据归一化,进一步提高了准确率,达到了 99.85%,如以下截图所示:
你可能会得到不同的结果,但可以多次运行训练步骤。以下是一些你可以在未来进行实验的步骤,以更好地理解网络:
-
尝试更好地调整超参数,并实施更高的丢弃率,看看网络如何响应。
-
当我们尝试使用不同的激活函数或较小(较稀疏)的网络时,准确性大幅下降。
-
同时,改变特征图和最大池化层的大小,观察这如何影响训练时间和模型准确性。
-
尝试在一个较稀疏的卷积神经网络中增加更多的神经元,并调整它以提高准确性。这也可能导致更快的网络,在更短的时间内完成训练。
-
使用更多的训练数据。探索其他在线仓库,找到更大的数据库来训练网络。卷积神经网络通常在训练数据量增大时表现更好。
另见
以下已发布的论文是了解卷积神经网络的好资源。它们可以作为进一步阅读材料,帮助你更深入地了解卷积神经网络在各类应用中的应用:
-
papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks -
www.cs.cmu.edu/~bhiksha/courses/deeplearning/Fall.2016/pdfs/Simard.pdf -
openaccess.thecvf.com/content_cvpr_2014/papers/Oquab_Learning_and_Transferring_2014_CVPR_paper.pdf -
www.aaai.org/ocs/index.php/IJCAI/IJCAI11/paper/download/3098/3425
第十一章:使用 Word2Vec 创建和可视化词向量
在本章中,我们将介绍以下内容:
-
获取数据
-
导入必要的库
-
数据准备
-
构建和训练模型
-
进一步可视化
-
进一步分析
介绍
在对文本数据进行神经网络训练并使用 LSTM 单元生成文本之前,理解文本数据(例如词语、句子、客户评论或故事)如何首先转换为词向量,并输入到神经网络中是非常重要的。本章将描述如何将文本转换为语料库,并从中生成词向量,使得使用欧几里得距离计算或余弦距离计算等技术将相似词语进行分组变得容易。
获取数据
第一步是获取一些数据来进行处理。在本章中,我们将需要大量的文本数据,将其转换为标记(tokens),并可视化以理解神经网络如何基于欧几里得距离和余弦相似度来对词向量进行排名。这是理解不同词语如何相互关联的重要步骤。反过来,这可以用于设计更好、更高效的语言和文本处理模型。
准备工作
请考虑以下内容:
-
模型的文本数据需要以
.txt格式保存文件,并且必须确保这些文件放在当前工作目录中。文本数据可以是任何内容,例如 Twitter 动态、新闻摘要、客户评论、计算机代码或保存在.txt格式中的完整书籍。在我们的例子中,我们使用了《权力的游戏》系列书籍作为模型的输入文本。然而,任何文本都可以代替书籍,且相同的模型仍然有效。 -
许多经典的文本已经不再受到版权保护。这意味着你可以免费下载这些书籍的所有文本,并将它们用于实验,例如创建生成模型。获得不再受版权保护的免费书籍的最佳网站是古腾堡计划(
www.gutenberg.org/)。
如何操作...
步骤如下:
- 首先访问古腾堡计划网站,浏览你感兴趣的书籍。点击书籍,然后点击 UTF-8,这样你就可以以纯文本格式下载该书籍。链接如以下截图所示:
古腾堡计划数据集下载页面
- 点击“Plain Text UTF-8”后,你应该会看到一个如下所示的页面。右键点击页面并选择“另存为...”,接下来,将文件重命名为你想要的名称,并保存到工作目录中:
-
现在,你应该能在当前工作目录中看到一个
.txt文件,文件名已按指定格式保存。 -
Project Gutenberg 在每本书的开头和结尾添加标准的页眉和页脚;这些并不是原始文本的一部分。请在文本编辑器中打开文件,删除页眉和页脚。
它是如何工作的...
该功能如下:
-
使用以下命令检查当前工作目录:
pwd。 -
工作目录可以使用
cd命令更改,如下所示截图:
-
请注意,在我们的案例中,文本文件存放在名为
USF的文件夹中,因此这被设置为工作目录。你也可以类似地将一个或多个.txt文件存放在工作目录中,作为模型的输入。 -
UTF-8 指定了文本文件中字符的编码类型。UTF-8 代表 Unicode Transformation Format。其中的 8 表示它使用 8 位 块来表示一个字符。
-
UTF-8 是一种折衷的字符编码,它可以像 ASCII 一样紧凑(如果文件仅为纯英文文本),但也能包含任何 Unicode 字符(尽管文件大小会有所增加)。
-
文本文件不需要是 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 环境中。这些库将使我们的任务变得更轻松,因为它们提供了现成的函数和模型,可以直接使用,而不需要我们自己实现。这样也使得代码更简洁、可读。
准备开始
以下是创建单词向量、绘制图形以及在二维空间中可视化 n 维单词向量所需的库和依赖项:
-
future -
codecs -
glob -
`multiprocessing` -
os -
`pprint` -
re -
nltk -
Word2Vec -
sklearn -
numpy -
matplotlib -
pandas -
seaborn
如何实现...
步骤如下:
- 在你的 Jupyter notebook 中键入以下命令来导入所有必需的库:
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
- 你应该看到如下截图中的输出:
- 接下来,使用以下命令导入
stopwords和punkt库:
nltk.download("punkt")
nltk.download("stopwords")
- 你看到的输出应如下图所示:
它是如何工作的...
本节将描述本配方中使用的每个库的目的。
-
future库是 Python 2 和 Python 3 之间的桥梁。它充当这两个版本之间的桥梁,允许我们使用这两个版本的语法。 -
codecs库将用于执行文本文件中所有单词的编码处理。这构成了我们的数据集。 -
Regex 是用于快速查找或搜索文件的库。
glob函数允许快速高效地在大型数据库中搜索所需的文件。 -
multiprocessing库允许我们执行并发处理,这是通过运行多个线程并让每个线程运行不同的进程来实现的。这是一种通过并行化使程序运行更快的方法。 -
os库允许与操作系统(如 Mac、Windows 等)轻松交互,并执行诸如读取文件等功能。 -
pprint库提供了一种漂亮打印任意 Python 数据结构的能力,格式化后可以作为解释器的输入。 -
re模块提供了类似于 Perl 中的正则表达式匹配操作。 -
NLTK 是一个自然语言工具包,能够用非常简短的代码对单词进行标记化。当输入一个完整的句子时,
nltk函数会将句子拆分并输出每个单词的标记。基于这些标记,单词可以被组织成不同的类别。NLTK 通过将每个单词与一个庞大的预训练词库(称为词典)进行比较来实现这一点。 -
Word2Vec是谷歌的模型,训练于一个巨大的词向量数据集。它将语义相似的词汇彼此靠近。这将是本节最重要的库。 -
sklearn.manifold通过使用t-分布随机邻居嵌入(t-SNE)技术来实现数据集的降维。由于每个词向量是多维的,我们需要某种形式的降维技术将这些词的维度降低到较低的空间,以便可以在二维空间中进行可视化。
还有更多内容...
Numpy 是一个常用的 math 库。Matplotlib 是我们将使用的 plotting 库,pandas 通过允许轻松地重塑、切片、索引、子集化和操作数据,提供了很大的灵活性。
Seaborn库是另一个统计数据可视化库,我们需要与matplotlib一起使用。Punkt和Stopwords是两个数据处理库,它们简化了任务,比如将语料库中的一段文本拆分为标记(即通过分词)并移除stopwords。
另见
有关所使用的某些库的更多信息,请访问以下链接:
数据准备
在将数据输入模型之前,需要进行一系列数据预处理步骤。本节将描述如何清理数据并准备好输入模型。
准备工作
所有来自.txt文件的文本首先会被转换成一个大的语料库。通过从每个文件中读取每个句子并将其添加到一个空的语料库中来实现。接着,会执行一系列预处理步骤,以去除不规则项,如空格、拼写错误、stopwords等。然后,清理过的文本数据需要被分词,分词后的句子会通过循环添加到一个空的数组中。
如何操作...
步骤如下:
- 输入以下命令,以在工作目录中查找
.txt文件并打印找到的文件名:
book_names = sorted(glob.glob("./*.txt"))
print("Found books:")
book_names
在我们的案例中,有五本书,分别名为got1、got2、got3、got4和got5,它们保存在工作目录中。
- 创建一个
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()
- 执行前述步骤中的代码,最终输出应该类似于以下截图所示:
- 使用以下命令从
punkt加载英语分词器tokenizer:
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
- 使用以下命令将整个
corpus分词为句子:
raw_sentences = tokenizer.tokenize(corpus)
- 定义一个函数,将句子拆分为其组成的单词,并以以下方式移除不必要的字符:
def sentence_to_wordlist(raw):
clean = re.sub("[^a-zA-Z]"," ", raw)
words = clean.split()
return words
- 将所有原始句子添加到一个新的句子数组中,每个句子的每个单词都已被分词。可以通过以下代码实现:
sentences = []
for raw_sentence in raw_sentences:
if len(raw_sentence) > 0:
sentences.append(sentence_to_wordlist(raw_sentence))
- 打印语料库中的一个随机句子,直观地看到
tokenizer如何拆分句子并从结果中创建单词列表。可以使用以下命令来实现:
print(raw_sentences[50])
print(sentence_to_wordlist(raw_sentences[50]))
- 使用以下命令统计数据集中的所有标记:
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 个分词。这在以下截图中得到了说明:
功能如下:
-
使用 NLTK 提供的预训练
tokenizer对整个语料库进行分词,通过将每个句子作为一个分词进行计数。每个分词的句子都被添加到变量raw_sentences中,该变量存储了所有分词后的句子。 -
下一步,将去除常见的停用词,并通过将每个句子拆分成单词来清理文本。
-
随机打印一个句子及其单词列表,以理解这一过程是如何工作的。在我们的例子中,我们选择打印
raw_sentences数组中的第 50 个句子。 -
统计并打印句子数组中的总分词数(在我们的例子中是句子)。在我们的案例中,我们看到
tokenizer创建了 1,110,288 个分词。
还有更多…
有关段落和句子分词的更多信息,请访问以下链接:
-
textminingonline.com/dive-into-nltk-part-ii-sentence-tokenize-and-word-tokenize -
stackoverflow.com/questions/37605710/tokenize-a-paragraph-into-sentence-and-then-into-words-in-nltk
另见
有关正则表达式工作原理的更多信息,请访问以下链接:
stackoverflow.com/questions/13090806/clean-line-of-punctuation-and-split-into-words-python
构建和训练模型
一旦我们拥有了以数组形式存储的分词数据,就可以将其输入到模型中。首先,我们需要为模型定义多个超参数。本节将介绍如何执行以下操作:
-
声明模型超参数
-
使用
Word2Vec构建模型 -
在准备好的数据集上训练模型
-
保存并检查训练过的模型
准备就绪
需要声明的一些模型超参数包括以下内容:
-
结果词向量的维度
-
最小词数阈值
-
在训练模型时运行的并行线程数
-
上下文窗口长度
-
下采样(针对频繁出现的词)
-
设置种子
一旦之前提到的超参数被声明,可以使用来自Gensim库的Word2Vec函数来构建模型。
如何操作...
步骤如下:
- 使用以下命令声明模型的超参数:
num_features = 300
min_word_count = 3
num_workers = multiprocessing.cpu_count()
context_size = 7
downsampling = 1e-3
seed = 1
- 使用声明的超参数构建模型,代码如下:
got2vec = w2v.Word2Vec(
sg=1,
seed=seed,
workers=num_workers,
size=num_features,
min_count=min_word_count,
window=context_size,
sample=downsampling
)
- 使用分词后的句子并遍历所有的词元来构建模型的词汇表。可以通过以下方式使用
build_vocab函数来完成:
got2vec.build_vocab(sentences,progress_per=10000, keep_raw_vocab=False, trim_rule=None)
- 使用以下命令训练模型:
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)
- 如果
trained目录尚未存在,则创建该目录。使用以下命令保存并检查点trained模型:
if not os.path.exists("trained"):
os.makedirs("trained")
got2vec.wv.save(os.path.join("trained", "got2vec.w2v"), ignore=[])
- 要在任何时刻加载保存的模型,使用以下命令:
got2vec = w2v.KeyedVectors.load(os.path.join("trained", "got2vec.w2v"))
它是如何工作的...
功能如下:
-
模型参数的声明不会产生任何输出,它仅仅是为存储模型参数的变量在内存中腾出空间。以下截图描述了这个过程:
-
模型是使用前述的超参数构建的。在我们的案例中,我们将模型命名为
got2vec,但模型的名称可以按您的喜好来命名。模型定义如下截图所示: -
在模型上运行
build_vocab命令后,应该会产生如下截图所示的输出: -
训练模型通过定义如下截图中的参数来完成:
-
上述命令会产生如下截图所示的输出:
- 保存、检查点和加载模型的命令会产生如下输出,如截图所示:
还有更多...
请考虑以下内容:
-
在我们的案例中,我们注意到
build_vocab函数从 1,110,288 个词中识别出 23,960 个不同的词类型。然而,这个数字会因文本语料的不同而有所变化。 -
每个词被表示为一个 300 维的向量,因为我们已经将维度声明为 300。增加这个数字会增加模型的训练时间,但也确保模型能更容易地推广到新数据。
-
发现 1e
3 的下采样率是一个不错的比率。该值用来告诉模型何时对频繁出现的词进行下采样,因为这些词在分析中并不重要。此类词的例子包括:this、that、those、them 等等。
-
设置一个种子以确保结果可重复。设置种子也能让调试变得更加容易。
-
训练模型大约需要 30 秒,使用普通 CPU 计算,因为模型不复杂。
-
模型在检查点时被保存在工作目录中的
trained文件夹下。
另见
欲了解更多关于Word2Vec模型和 Gensim 库的信息,请访问以下链接:
radimrehurek.com/gensim/models/word2vec.html
进一步可视化
本节将描述如何将所有训练好的词的维度压缩,并将其放入一个巨大的矩阵中进行可视化。由于每个词是一个 300 维的向量,因此需要将其降维到较低的维度,以便我们在二维空间中可视化它。
准备工作
在模型保存并检查点训练完成后,像上一节那样开始将模型加载到内存中。本节将使用的库和模块有:
-
tSNE -
pandas -
Seaborn -
numpy
如何操作...
步骤如下:
- 使用以下命令压缩 300 维词向量的维度:
tsne = sklearn.manifold.TSNE(n_components=2, random_state=0)
- 将所有词向量放入一个巨大的矩阵(命名为
all_word_vectors_matrix)中,并使用以下命令查看:
all_word_vectors_matrix = got2vec.wv.syn0
print (all_word_vectors_matrix)
- 使用
tsne技术通过以下命令将所有学习到的表示拟合到二维空间中:
all_word_vectors_matrix_2d = tsne.fit_transform(all_word_vectors_matrix)
- 使用以下代码收集所有的词向量以及它们关联的词:
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"]
)
- 可以使用以下命令获取前十个点的
X和Y坐标及其关联的词:
points.head(10)
- 使用以下命令绘制所有点:
sns.set_context("poster")
points.plot.scatter("x", "y", s=10, figsize=(15, 15))
- 可以放大绘制图中的选定区域进行更细致的检查。通过以下函数切片原始数据来实现这一点:
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)
- 使用以下命令绘制切片后的数据。切片数据可以被视为原始图中所有数据点的放大区域:
plot_region(x_bounds=(20.0, 25.0), y_bounds=(15.5, 20.0))
它是如何工作的...
功能如下:
-
t-SNE 算法是一种非线性降维技术。计算机在处理计算时可以轻松处理多个维度。然而,人类只能一次性可视化两到三维。因此,在尝试从数据中提取洞见时,这些降维技术非常有用。
-
将 t-SNE 应用于 300 维的词向量后,我们能够将其压缩成二维,进行绘图并查看。
-
通过将
n_components指定为 2,我们告诉算法必须将数据压缩到二维空间。一旦完成,我们将所有压缩后的向量加入到一个名为all_word_vectors_matrix的巨型矩阵中,如下图所示: -
t-SNE 算法需要对所有这些词向量进行训练。使用普通 CPU 训练大约需要五分钟。
-
一旦 t-SNE 在所有词向量上完成训练,它会输出每个单词的 2D 向量。这些向量可以通过将它们转换为数据框来作为点进行绘制。具体做法如下图所示:
-
我们看到,前面的代码生成了多个点,每个点代表一个单词及其 X 和 Y 坐标。检查数据框中的前二十个点时,输出如下图所示:
-
使用
all_word_vectors_2D变量绘制所有点时,你应该会看到一个类似于以下截图的输出: -
上述命令将生成整个文本中所有标记或单词的图表,如下图所示:
-
我们可以使用
plot_region函数来缩放到图表的某个区域,这样就可以实际看到单词及其坐标。此步骤如以下截图所示: -
可以通过设置
x_bounds和y_bounds值来放大或缩小图表的区域,如下图所示: -
可以通过更改
x_bounds和y_bounds值来可视化同一图表的不同区域,如下图所示:
另请参见
下面是一些需要注意的附加要点:
-
要了解有关 t-SNE 算法如何工作的更多信息,请访问以下链接:
-
www.oreilly.com/learning/an-illustrated-introduction-to-the-t-sne-algorithm -
有关余弦距离相似度和排序的更多信息,请访问以下链接:
-
使用以下链接来探索
Seaborn库的不同功能:
进一步分析
本节将描述在可视化后可以对数据执行的进一步分析。例如,探索不同词向量之间的余弦距离相似度。
准备开始
以下链接是一个很好的博客,讲解了余弦距离相似度的原理,并讨论了一些相关的数学内容:
如何做到...
请考虑以下内容:
-
使用
Word2Vec的不同功能,可以执行各种自然语言处理任务。其中一个任务是给定某个单词后,找出最语义相似的单词(即,具有高余弦相似度或较短欧几里得距离的词向量)。这可以通过使用Word2Vec的most_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")
-
上述过程在以下截图中进行了说明:
-
结果如下:
-
如本节所示,词向量构成了所有自然语言处理(NLP)任务的基础。在深入研究更复杂的 NLP 模型,如递归神经网络和长短期记忆(LSTM)单元之前,理解词向量及其构建数学模型是非常重要的。
另见
为了更好地理解余弦距离相似度、聚类和用于排序词向量的其他机器学习技术,可以进一步阅读以下内容。以下是一些关于此主题的有用论文链接:
第十二章:使用 Keras 创建电影推荐引擎
本章将涵盖以下内容:
-
下载 MovieLens 数据集
-
操作和合并 MovieLens 数据集
-
探索 MovieLens 数据集
-
为深度学习管道准备数据集
-
使用 Keras 应用深度学习管道
-
评估推荐引擎的准确性
介绍
2006 年,一家小型 DVD 租赁公司开始致力于让他们的推荐引擎提升 10%。这家公司就是 Netflix,而 Netflix 奖的奖金为 100 万美元。这个竞赛吸引了来自全球一些最大科技公司的工程师和科学家。获胜者的推荐引擎是用机器学习构建的。如今,Netflix 已经成为流媒体数据和向客户推荐下一部观看内容的科技巨头之一。
现在,评分无处不在,不管你在做什么。如果你正在寻找推荐去新餐馆吃饭、在线购买衣物、在当地影院观看新电影,或者在电视或网络上观看新剧集,极有可能会有一个网站或移动应用提供一些评分以及你所要购买的产品或服务的反馈。正是因为这种即时反馈的增加,推荐算法在过去几年变得更加受欢迎。本章将重点讲解如何使用深度学习库 Keras 为用户构建一个电影推荐引擎。
下载 MovieLens 数据集
有一个很棒的研究实验室,成立于 1992 年,位于美国明尼阿波利斯市,名为GroupLens,专注于推荐引擎,并慷慨地从 MovieLens 网站收集了数百万行数据。我们将使用它的数据集作为训练我们推荐引擎模型的数据源。
准备工作
MovieLens 数据集由 GroupLens 托管和维护,网址如下:
grouplens.org/datasets/movielens/。
需要注意的是,我们将使用的数据集将直接来自他们的网站,而不是来自第三方中介或仓库。此外,有两个不同的数据集供我们查询:
-
推荐用于新研究
-
推荐用于教育和开发
使用这个数据集的目的是纯粹为了教育目的,因此我们将从网站的教育和开发部分下载数据。教育数据仍包含足够多的行供我们的模型使用,因为它包含了 100,000 条评分,如下图所示:
此外,该数据集包含了超过 600 个匿名用户的信息,这些信息是从 1995 年 1 月 9 日到 2015 年 3 月 31 日这段时间内收集的。数据集最后一次更新是在 2017 年 10 月。
F Maxwell Harper 和 Joseph A Konstan, 2015. The MovieLens 数据集:历史与背景。ACM 交互智能系统期刊 (TiiS) 5, 4, 文章 19(2015 年 12 月),第 19 页。DOI:dx.doi.org/10.1145/2827872
如何操作...
本节将介绍如何下载并解压 MovieLens 数据集:
-
下载 MovieLens 数据集的研究版本,该版本可从以下网址公开下载:
grouplens.org/datasets/movielens/latest/。 -
将名为
ml-latest-small.zip的ZIP文件下载到我们本地的某个文件夹中,如以下截图所示:
-
当
ml-latest-small.zip被下载并解压时,应该提取以下四个文件:-
links.csv -
movies.csv -
ratings.csv -
`tags.csv`
-
-
执行以下脚本来启动我们的
SparkSession:
spark = SparkSession.builder \
.master("local") \
.appName("RecommendationEngine") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
- 通过执行以下脚本,确认以下六个文件可以访问:
import os
os.listdir('ml-latest-small/')
- 使用以下脚本将每个数据集加载到 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')
- 通过执行以下脚本,确认每个数据集的行数:
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 数据集中每个数据集中的字段。请查看以下步骤:
- 数据集都可以在压缩文件
ml-latest-small.zip中找到,其中ratings.csv数据集将作为我们数据的伪事实表,因为它包含每个评分电影的交易记录。数据集ratings有以下四个列名,见下图:
- 数据集展示了每个 userId 在其使用期间的评分,从最早的评分到最新的评分。评分范围从 0.5 到 5.0 星,以下截图显示了
userId = 1的评分:
tags数据集包含一个标签列,其中包含用户用来描述特定 movieId 在特定时间戳下的特定单词或短语。如下图所示,userId 15 对 Sandra Bullock 在其一部电影中的表现并不特别喜爱:
movies数据集主要是一个查找表,用于电影类型与评分之间的关联。电影可以关联 19 种独特的类型;然而,需要注意的是,一部电影可以同时关联多种类型,如以下截图所示:
- 最终的数据集是
links数据集,它也充当一个查找表。它将 MovieLens 中的电影与这些电影在流行电影数据库网站上的相关数据连接起来,比如www.imdb.com,以及www.themoviedb.org。IMDB 的链接位于名为 imdbId 的列下,MovieDB 的链接位于名为 tmdbId 的列下,如下图所示:
- 在我们完成之前,确认我们确实得到了期望的行数总是一个好主意,这有助于确保我们在上传文件到笔记本时没有遇到任何问题。我们应该期望看到约 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 大奖的信息,请访问以下网站:
操作和合并 MovieLens 数据集
目前我们有四个独立的数据集,但最终我们希望将它们整合成一个数据集。本章将专注于将数据集缩减为一个。
准备工作
本节不需要导入 PySpark 库,但了解 SQL 连接的背景知识会非常有帮助,因为我们将探索多种连接数据框的方法。
如何做到……
本节将介绍以下步骤来连接 PySpark 中的数据框:
- 执行以下脚本将
ratings中的所有字段名称重命名,在名称末尾附加一个_1:
for i in ratings.columns:
ratings = ratings.withColumnRenamed(i, i+'_1')
- 执行以下脚本将
movies数据集与ratings数据集进行inner join,创建一个新的表格,名为temp1:
temp1 = ratings.join(movies, ratings.movieId_1 == movies.movieId, how = 'inner')
- 执行以下脚本将
temp1数据集与links数据集进行内连接,创建一个新的表格,名为temp2:
temp2 = temp1.join(links, temp1.movieId_1 == links.movieId, how = 'inner')
- 通过以下脚本,将
temp2与tags左连接,创建我们的最终合并数据集mainDF:
mainDF = temp2.join(tags, (temp2.userId_1 == tags.userId) & (temp2.movieId_1 == tags.movieId), how = 'left')
- 通过执行以下脚本,仅选择我们最终
mainDF数据集所需的列:
mainDF = mainDF.select('userId_1',
'movieId_1',
'rating_1',
'title',
'genres',
'imdbId',
'tmdbId',
'timestamp_1').distinct()
它是如何工作的…
本节将介绍我们将表格连接在一起的设计过程,以及哪些最终列会被保留:
- 正如前一节中提到的,
ratings数据框将作为我们的事实表,因为它包含每个用户随着时间推移的所有主要评分交易。ratings中的列将用于与其他三个表的每次后续连接,为了保持列的唯一性,我们将给每个列名后加上_1,如以下截图所示:
- 我们现在可以将三个查找表与评分表连接。前两个与评分表的连接是内连接,因为
temp1和temp2的行数仍为 100,004 行。从tags表连接的第三个连接需要是外连接,以避免丢失行。此外,连接需要应用于movieId和userId,因为一个标签在任何给定时间都是特定用户和特定电影唯一的。三个表temp1、temp2和mainDF的行数可以在下图中看到:
在处理数据集之间的连接时,我们经常会遇到三种类型的连接:内连接(inner)、左连接(left)和右连接(right)。内连接只有在数据集 1 和数据集 2 的连接键都可用时,才会生成结果集。左连接将生成数据集 1 中的所有行,以及仅包含与数据集 2 中的匹配键的行。右连接将生成数据集 2 中的所有行,以及仅包含与数据集 1 中的匹配键的行。稍后在本节中,我们将探索 Spark 中的 SQL 连接。
- 有趣的是,我们新创建的数据集
mainDF有 100,441 行,而不是原始ratings数据集中的 100,004 行,以及temp1和temp2。其中有 437 个评分与多个标签相关联。此外,我们还可以看到,大多数ratings_1的tag值为 null,如下图所示:
- 我们积累了不再需要的重复列。共有 14 列,如下图所示:
- 此外,我们已经确定,
tags字段相对没有用,因为它有超过 99k 的空值。因此,我们将使用select()函数从数据框中仅提取我们将用于推荐引擎的八列。然后,我们可以确认我们的最终新数据框mainDF具有正确的行数 100,004,如下图所示:
还有更多…
尽管我们通过在 Spark dataframe 中使用 PySpark 的函数进行了连接,但我们也可以通过将数据框注册为临时表,然后使用sqlContext.sql()进行连接:
- 首先,我们将使用
creatorReplaceTempView()将每个数据集注册为临时视图,如下脚本所示:
movies.createOrReplaceTempView('movies_')
links.createOrReplaceTempView('links_')
ratings.createOrReplaceTempView('ratings_')
- 接下来,我们将像操作任何其他关系数据库一样,使用
sqlContext.sql()函数编写 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
"""
)
- 最后,我们可以对新的数据框
mainDF_SQL进行分析,观察其与另一个数据框mainDF相同,且行数保持一致,如下截图所示:
另见
要了解更多关于 Spark 中 SQL 编程的信息,请访问以下网站:
spark.apache.org/docs/latest/sql-programming-guide.html
探索 MovieLens 数据集
在进行任何建模之前,熟悉源数据集并进行一些探索性数据分析是非常重要的。
准备工作
我们将导入以下库来帮助可视化和探索 MovieLens 数据集:matplotlib。
如何操作……
本节将逐步讲解如何分析 MovieLens 数据库中的电影评分:
- 通过执行以下脚本,获取
rating_1列的一些汇总统计数据:
mainDF.describe('rating_1').show
- 通过执行以下脚本,构建评分分布的直方图:
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()
- 执行以下脚本,以在电子表格数据框中查看直方图的值:
mainDF.groupBy(['rating_1']).agg({'rating_1':'count'})\
.withColumnRenamed('count(rating_1)', 'Row Count').orderBy(["Row Count"],ascending=False)\
.show()
- 用户对评分选择的唯一计数可以通过执行以下脚本存储为数据框
userId_frequency:
userId_frequency = mainDF.groupBy(['userId_1']).agg({'rating_1':'count'})\
.withColumnRenamed('count(rating_1)', '# of Reviews').orderBy(["# of Reviews"],ascending=False)
- 使用以下脚本绘制
userID_frequency的直方图:
userId_frequency.select('# of Reviews').toPandas().hist(figsize=(16, 6), grid=True)
plt.title('Histogram of User Ratings')
plt.show()
它是如何工作的……
本节将讨论 MovieLens 数据库中评分和用户活动的分布。请查看以下步骤:
- 我们可以看到,用户对电影的平均评分约为 3.5,如下截图所示:
- 尽管平均评分为 3.54,但我们可以看到直方图显示中位数评分为 4,这表明用户评分明显偏向较高的评分,如下截图所示:
- 通过对直方图背后的数据进行进一步分析,我们可以看到用户最常选择 4.0 评分,其次是 3.0,再然后是 5.0。此外,有趣的是,用户更倾向于给出 0.0 评分,而非 0.5 评分,如下图所示:
- 我们可以查看用户评分选择的分布,看到一些用户在表达他们对电影的看法时非常活跃。例如,匿名用户 547 就发布了 2391 条评分,以下截图为证:
- 然而,当我们查看用户评分选择的分布时,我们确实发现,尽管有些用户自己做出了超过一千个选择,但绝大多数用户的选择次数少于 250 次,以下截图可见一斑:
- 上一张截图中直方图的分布呈长尾格式,表明大多数数据点位于直方图中心的两侧。这表明绝大多数评分是由少数用户定义的。
还有更多…
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
如何操作...
本节通过以下步骤讲解如何为深度学习管道准备数据集:
- 执行以下脚本来清理列名:
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')
rating列目前按 0.5 的增量进行划分。使用以下脚本将评分调整为四舍五入到整数:
import pyspark.sql.functions as F
mainDF = mainDF.withColumn("rating", F.round(mainDF["rating"], 0))
- 使用以下脚本将
genres列从字符串转换为基于genres标签频率的索引,并命名为genreCount:
from pyspark.ml.feature import StringIndexer
string_indexer = StringIndexer(inputCol="genres", outputCol="genreCount")
mainDF = string_indexer.fit(mainDF).transform(mainDF)
- 使用以下脚本对我们的数据框进行精简:
mainDF = mainDF.select('rating', 'userid', 'movieid', 'imdbid', 'tmdbid', 'timestamp', 'genreCount')
- 使用以下脚本将
mainDF分割为训练集和测试集,以便用于模型训练:
trainDF, testDF = mainDF.randomSplit([0.8, 0.2], seed=1234)
- 使用以下脚本将我们的两个 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()
- 使用以下脚本将
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)
它是如何工作的...
本节说明了如何为深度学习管道准备数据集:
- 为了在深度学习管道中更方便地使用,最好在数据传入管道之前清理列名和列的顺序。重命名列标题后,我们可以查看更新后的列,如以下脚本所示:
-
对
ratings列进行了一些操作,将 0.5 的增量值向上舍入到下一个整数。这将有助于在 Keras 中进行多分类时,将ratings分为六个类别,而不是 11 个类别。 -
为了将电影类型传入深度学习模型,我们需要将
genres的字符串值转换为数值标签。最常见的电影类型将被赋值为 0,其他类型依次增加值。在下图中,我们可以看到《心灵捕手》有两个关联的类型(剧情 | 爱情),这两种类型是第四常见的电影类型,genreCount的值为 3.0:
- 对于深度模型来说,
genres列不再需要,因为它将被genreCount列替代,如下图所示:
- 我们的主要数据框
mainDF被分割为trainDF和testDF,用于建模、训练和评估,采用 80/20 的比例划分。三个数据框的行数可以从下图看到:
- 数据被传入 Keras 深度学习模型,使用矩阵而非数据框。因此,我们的训练和测试数据框被转换为 numpy 数组,并被拆分为 x 和 y。
xtrain_array和xtest_array选取的特征为 userid、movieid 和 genreCount。这些是我们用来预测用户可能评分的唯一特征。我们会丢弃imdbid和tmdbid,因为它们直接与movieid相关,因此不会提供额外的价值。timestamp会被移除,以过滤与投票频率相关的偏差。最后,ytest_array和ytrain_array将包含评分的标签值。所有四个数组的shape如下图所示:
还有更多内容...
虽然 ytrain_array 和 ytest_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 在数据集上应用深度学习模型的步骤:
- 导入以下库,以从
keras构建一个Sequential模型,使用以下脚本:
from keras.models import Sequential
from keras.layers import Dense, Activation
- 使用以下脚本从
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'])
- 我们使用以下脚本
fit并训练模型,并将结果存储到一个名为accuracy_history的变量中:
accuracy_history = model.fit(xtrain_array, ytrain_OHE, epochs=20, batch_size=32)
工作原理...
本节解释了将 Keras 模型应用于数据集的配置,目的是根据选定的特征预测评分。
-
在 Keras 中,
Sequential模型只是各层的线性组合,具体如下:Dense用于定义深度神经网络中的全连接层。最后,Activation用于将输入的特征转换为可以作为预测结果的输出。神经网络中可以使用许多类型的激活函数;然而,在本章中,我们将使用relu和softmax。 -
Sequential模型被配置为包括三个Dense层:-
第一个层的
input_dim设置为来自xtrain_array的特征数量。shape特性使用xtrain_array.shape[1]拉取值 3。此外,第一层设置为在神经网络的第一层中有32个神经元。最后,三个输入参数使用relu激活函数进行激活。只有第一层需要显式定义输入维度。后续层不需要,因为它们可以从前一层推断维度的数量。 -
Sequential模型的第二层在神经网络中有10个神经元,并且激活函数设置为relu。修正线性单元(ReLU)通常在神经网络的早期阶段使用,因为它们在训练过程中非常有效。这是因为该函数的方程简单,任何小于 0 的值都会被抛弃,而其他激活函数并非如此。 -
Sequential模型的第三层和最后一层需要基于从 0 到 5 的评分所有可能的情况输出六个结果。这需要将输出设置为ytrain_OHE.shape[1]的值。输出通过softmax函数生成,这通常是在神经网络的最后一层,因为它在分类任务中非常有用。此时,我们的目标是对 0 到 5 之间的值进行分类。 -
一旦指定了各层,我们就必须
compile模型。 -
我们使用
adam优化模型,adam代表自适应矩估计。优化器对于配置模型使用的梯度下降学习率非常有效,梯度下降用来调整和更新神经网络的权重。adam是一种流行的优化器,据说它结合了其他常见优化器的一些最佳特性。 -
我们的损失函数设置为
categorical_crossentropy,该损失函数通常用于多类分类预测。损失函数评估模型在训练过程中的表现。
-
-
我们使用训练特征
xtrain_array和训练标签ytrain_OHE训练模型。模型在 20 个训练周期内进行训练,每次批次大小(batch_size)设为 32。每个训练周期的accuracy和loss输出被捕获在一个名为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()
脚本的输出可以在以下截图中看到:
看起来在第二个训练周期(epoch)之后,模型的损失和准确率都已经稳定下来。
另见
要了解更多关于如何开始使用 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 指标评估模型性能的更多信息,请访问以下网站:
第十三章:使用 TensorFlow 和 Spark 进行图像分类
本章将涵盖以下内容:
-
下载梅西和 C 罗各 30 张图片
-
配置带深度学习包的 PySpark 安装
-
将图片加载到 PySpark 数据框中
-
理解迁移学习
-
创建图像分类训练的流水线
-
评估模型性能
-
微调模型参数
介绍
在过去几年中,图像识别软件的需求日益增长。这种需求的增加与大数据存储的进展不无关系。Google Photos、Facebook 和 Apple 都使用图像分类软件来为用户标记照片。这些公司使用的大部分图像识别软件都是基于流行的库,如 TensorFlow,使用深度学习模型构建的。本章通过利用一组图像的训练来学习或识别另一组图像,拓展了深度学习的技术。这个概念被称为迁移学习。在本章中,我们将重点介绍如何利用迁移学习来识别世界上排名前两的足球运动员:
-
利昂内尔·梅西
-
克里斯蒂亚诺·罗纳尔多
看一下这张照片:
下载梅西和 C 罗各 30 张图片
在进行任何图片分类之前,我们必须先从网上下载我们足球运动员的图片。
准备就绪
有几个浏览器插件可以批量下载图片。由于 Ubuntu 预装了 Mozilla Firefox 浏览器,我们将使用它作为首选浏览器来安装批量图片下载扩展。
如何操作...
以下部分解释了如何批量下载图片。请参考这些步骤:
- 访问以下网站以下载和安装 Firefox 插件:
addons.mozilla.org/en-US/firefox/
- 搜索并选择“下载所有图片”插件,如下面的截图所示:
- 这将带我们到安装页面。此时,选择“添加到 Firefox”,如下面的截图所示:
-
确认你的安装,因为这个插件将需要权限访问你的浏览器下载历史,访问所有网站的数据,并发送通知。
-
一旦完成,你应该会在浏览器右上角看到一个小的图片图标,表示“下载所有图片”,如下面的截图所示:
- 我们现在准备好开始下载我们的足球运动员的图片了,使用的是新安装的 Firefox 扩展。我们可以访问许多不同的网站来下载图片,例如
www.google.com。为了本章节的目的,搜索克里斯蒂亚诺·罗纳尔多,并通过www.pexels.com下载他的图片,如下图所示:
- 接下来,点击“下载所有图片”图标,并按照下图所示指定图片的下载设置:
- 点击“保存”,这样你将能够选择将所有图片下载为一个
.zip文件并保存到本地目录。然后,你可以将该文件解压到一个文件夹,并浏览所有图片。在我们的示例中,所有图片已经被提取到/Home/sparkNotebooks/Ch13/football/ronaldo/,如下图所示:
- 在文件夹中所有可用的图片中,选择 30 张罗纳尔多的图片,并将其命名为
ronaldo1.jpg、ronaldo2.jpg…ronaldo30.jpg,如下图所示:
- 再次重复步骤,这次为梅西获取 30 张图片。最终的文件夹结构应该如下所示:
其工作原理...
本节解释了插件如何将图片批量下载到我们所需位置的过程:
-
目前有很多批量图片下载软件,并且已经集成到浏览器中。我们将使用 Firefox 的“下载所有图片”插件,快速下载梅西和罗纳尔多的图片。
-
我们想要在应用中设置下载低质量的图片,因此我们设置了最小阈值为 0 字节,最大阈值为 500 字节,并将图片类型设置为
jpg或jpeg。 -
最后,我们希望精心挑选出最能代表每位球员的 30 张图片,其中 20 张将作为训练数据集,剩余的 10 张将作为测试数据集。其他所有图片都可以删除。
-
所有的图片将按姓氏和 1 到 30 之间的数字进行标记或标签,以便于训练使用。例如,
Messi1.jpg、Messi2.jpg、Ronaldo1.jpg、Ronaldo2.jpg,依此类推。
还有更多内容...
尽管你可以使用你自己下载的图片,也可以通过访问以下网站下载梅西和罗纳尔多的图片,这些图片将用于本章节的训练目的:
对于梅西:
github.com/asherif844/ApacheSparkDeepLearningCookbook/tree/master/CH13/football/messi
对于罗纳尔多:
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 与深度学习包:
- 打开终端应用程序,并输入以下命令:
nano .bashrc.
-
滚动到文档底部,并查找我们在第一章中创建的
sparknotebook()函数,为深度学习设置您的 Spark 环境。 -
更新函数的最后一行。当前应如下所示:
$SPARK_HOME/bin/pyspark.
将其更改为以下内容:
$SPARK_HOME/bin/pyspark --packages databricks:spark-deep-learning:0.1.0-spark2.1-s_2.11.
- 一旦配置更改完成,退出文档并执行以下脚本以确认所有必要的更改已保存:
source .bashrc.
工作原理……
下面的部分解释了如何修改 PySpark 以整合深度学习包,请看这些步骤:
- 访问 bash 允许我们在命令行上进行配置,就像下面的屏幕截图所示:
-
在我们的文档末尾,我们可以看到我们原始的函数
sparknotebook()仍然完好无损;然而,我们需要修改它以整合spark-deep-learning包。 -
由于这次修改直接针对 PySpark,而不是 Python 库,我们无法通过典型的
pip安装将其整合到我们的框架中。相反,我们将修改我们的 PySpark 配置,使其显示如下屏幕截图所示:
- 我们现在已经配置了 PySpark 安装,以整合包含各种解决方案模型构建 API 的深度学习库,如图像分类。
还有更多……
这个包,spark-deep-learning,由Databricks管理。Databricks 是由 Spark 的共同创始人之一 Ali Ghodsi 创立的,用于通过统一平台提供托管的 Spark 解决方案。
另请参阅
若想了解更多为 Spark 开发的其他第三方包,请访问以下网站:
加载图像到 PySpark 数据框中
我们现在已经准备好将我们的图像导入笔记本进行分类。
准备工作
在这一部分,我们将使用几个库及其依赖项,这将要求我们在 Ubuntu 桌面中的终端通过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 数据框:
- 启动一个
spark会话,使用以下脚本:
spark = SparkSession.builder \
.master("local") \
.appName("ImageClassification") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
- 从 PySpark 中导入以下库以创建数据框,使用以下脚本:
import pyspark.sql.functions as f
import sparkdl as dl
- 执行以下脚本,创建两个数据框,分别用于梅西和罗纳尔多,并使用每个球员的主文件夹位置:
dfMessi = dl.readImages('football/messi/').withColumn('label', f.lit(0))
dfRonaldo = dl.readImages('football/ronaldo/').withColumn('label', f.lit(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)
- 最后,使用以下脚本将训练数据框和测试数据框合并为两个新的数据框,
trainDF和testDF:
trainDF = trainDFmessi.unionAll(trainDFronaldo)
testDF = testDFmessi.unionAll(testDFronaldo)
它是如何工作的...
以下部分解释了如何将图像加载并读取到 Jupyter 笔记本中。请查看这些步骤:
-
我们总是通过启动 Spark 会话来开始一个 Spark 项目,以设置应用程序名称并设置 Spark 执行内存。
-
我们导入了
pyspark.sql.functions和sparkdl,以帮助基于编码图像构建数据框。当导入sparkdl时,我们看到它在后端使用了 TensorFlow,如下图所示:
- 数据框使用
sparkdl创建,包含三列:文件路径、图像和标签。Sparkdl 用于导入每个图像,并通过颜色和形状对其进行编码。此外,lit函数用于为训练目的,在标签列下为每个数据框添加一个字面值(0 或 1),如下图所示:
- 由于每位足球运动员有 30 张图片,采用 66.7/33.3 的比例来创建 18 张训练图像和 12 张测试图像,如下图所示:
请注意,在深度学习中,使用更多的图像进行训练会更好。然而,我们在本章中要证明的是,随着迁移学习作为深度学习的扩展的实施,我们可以使用更少的训练样本来分类图像,正如本章中梅西和罗纳尔多每人只有 30 张图片的情况。
- 为了构建我们的模型,我们只关心创建一个包含 36 张图片的训练数据框,以及一个包含其余 24 张图片的测试数据框。一旦我们合并了数据框,可以确认它们的大小是正确的,如下图所示:
还有更多…
这可能在过程中丢失,但重要的是要注意,将图像加载到数据框架中非常简单,只需要几行代码,使用 sparkdl.readImages。这展示了 Spark 提供的机器学习管道的强大功能。
另请参见
要了解更多关于 sparkdl包的信息,请访问以下仓库:
databricks.github.io/spark-deep-learning/site/api/python/sparkdl.html
理解迁移学习
本章剩余部分将涉及迁移学习技术;因此,我们将在本节中解释迁移学习如何在我们的架构中工作。
准备工作
本节不需要任何依赖项。
如何做到这一点…
本节介绍了迁移学习的工作步骤:
-
确定一个预训练模型,将其作为迁移到我们选择任务的训练方法。在我们的例子中,任务将是识别梅西和罗纳尔多的图像。
-
有多个可用的预训练模型可以使用。最流行的几个如下:
-
Xception
-
InceptionV3
-
ResNet50
-
VGG16
-
VGG19
-
-
从预训练的卷积神经网络中提取特征,并在多个过滤和池化层中保存这些特征,应用于特定的图像集。
-
预训练卷积神经网络的最终层被替换为我们基于数据集要分类的特定特征。
它是如何工作的…
本节解释了迁移学习的方法论:
-
在前几章中,我们讨论了机器学习模型,尤其是深度学习模型,如何在较大样本的训练中表现最佳。事实上,深度学习的一般格言是:越多越好。
-
然而,有些情况下可能没有足够的高量数据或图像来训练一个模型。正是在这种情况下,我们希望将一个领域的学习迁移到预测另一个领域的结果。提取特征并通过卷积神经网络中的多个层进行过滤的繁重工作已经由一些开发了许多预训练模型的机构(如 InceptionV3 和 ResNet50)完成。
-
InceptionV3 是在谷歌开发的,权重比 ResNet50 和 VGG 小。
-
ResNet50 使用 50 个权重层
-
VGG16 和 VGG19 分别具有 16 个和 19 个权重层
-
-
一些高级深度学习库,如 Keras,现在预先构建了这些预训练网络,简化了应用,只需要指定模型名称。
还有更多…
确定哪种预训练模型最适合特定数据或图像集,取决于所使用的图像类型。最好尝试不同的预训练模型,确定哪个模型能提供最佳的准确度。
另请参见
要了解更多关于 Inception V3 预训练模型的信息,请阅读以下论文:
要了解更多关于 VGG 预训练模型的信息,请阅读以下论文:
创建图像分类训练管道
我们现在准备好为我们的数据集构建深度学习管道。
准备工作
以下库将被导入,以协助管道开发:
-
LogisticRegression -
Pipeline
如何操作…
以下部分将讲解创建图像分类管道的步骤:
- 执行以下脚本以开始深度学习管道并配置分类参数:
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)
- 创建一个新的数据框
predictDF,其中包含原始测试标签以及新的预测分数,使用以下脚本:
predictDF = pipeline_model.transform(testDF)
predictDF.select('prediction', 'label').show(n = testDF.toPandas().shape[0], truncate=False)
它是如何工作的…
以下部分解释了如何为图像分类配置管道,以实现最佳性能:
LogisticRegression已被导入,因为它将是区分梅西和罗纳尔多图像的主要分类算法。DeepImageFeaturizer从sparkdl导入,用于基于图像创建特征,这些特征将作为逻辑回归算法的最终输入。
需要注意的是,通过DeepImageFeaturizer创建的特征将使用基于InceptionV3的预训练模型,并分配一个名为vectorizer的变量。
逻辑回归模型的调优次数最多为 30 次迭代。最后,管道将vectorizer和LogisticRegression变量输入并拟合到训练数据框trainDF中。vectorizer用于将图像转化为数值。DeepImageFeaturizer的输出可见于以下截图:
- 测试数据框
testDF通过应用拟合后的管道模型pipeline_model,转换成新的数据框predictDF,并创建一个名为“prediction”的新列。我们可以将标签列与预测列进行比较,如下截图所示:
还有更多…
InceptionV3是我们用于图像分类的图像分类模型;然而,我们完全可以选择其他预训练模型,并在管道中比较准确性。
另请参阅
要了解更多关于迁移学习的信息,请阅读威斯康星大学的以下文章:
ftp.cs.wisc.edu/machine-learning/shavlik-group/torrey.handbook09.pdf
评估模型性能
我们已经准备好评估我们的模型,看看我们能多好地区分梅西和罗纳尔多。
准备工作
由于我们将进行一些模型评估,因此需要导入以下库:
MulticlassClassificationEvaluator
如何操作...
以下部分介绍了评估模型性能的步骤:
- 执行以下脚本,从
predictDF数据框创建混淆矩阵:
predictDF.crosstab('prediction', 'label').show().
- 通过执行以下脚本,基于我们的 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))).
工作原理...
以下部分解释了如何评估模型性能。请查看这些图像:
- 我们可以将数据框
predictDF转换为交叉表,创建混淆矩阵。这让我们能够理解模型中有多少真正例、假正例、真反例和假反例,如下图所示:
- 此时,我们准备好计算使用 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
微调模型参数
任何模型的准确率总是有提升空间。在本节中,我们将讨论一些可以调整的参数,以提高我们从上一节得到的 87.5%的模型准确率。
准备工作
本节不需要任何新的前提条件。
如何操作...
本节介绍了如何微调模型的步骤。
- 定义一个新的逻辑回归模型,添加
regParam和elasticNetParam参数,如下脚本所示:
logregFT = LogisticRegression(
regParam=0.05,
elasticNetParam=0.3,
maxIter=15,labelCol = "label", featuresCol="features")
- 使用以下脚本为新创建的模型配置一个新的管道:
pipelineFT = Pipeline(stages=[vectorizer, logregFT])
- 使用以下脚本将管道拟合到训练数据集
trainDF:
pipeline_model_FT = pipelineFT.fit(trainDF)
- 应用模型转换到测试数据集
testDF,以便使用以下脚本比较实际与预测的得分:
predictDF_FT = pipeline_model_FT.transform(testDF)
predictDF_FT.crosstab('prediction', 'label').show()
- 最后,使用以下脚本评估新模型的准确率
binary_rate_FT:
binary_rate_FT = binaryevaluator.evaluate(predictDF_FT)*100
print("accuracy: {}%" .format(round(binary_rate_FT,2)))
它是如何工作的…
本节解释了如何微调模型:
-
逻辑回归模型
logregFT通过调整regParam和elasticNetParam参数进行微调。两个参数分别对应逻辑回归模型的 γ 和 α 参数。正则化参数regParam用于在最小化损失函数和减少模型过拟合之间找到平衡。我们越是让模型复杂化,它就越容易过拟合而无法泛化,但也可能会得到较低的训练误差。此外,我们越简化模型,它过拟合的可能性就越小,但训练误差可能会更高。 -
弹性网络参数
elasticNetParam是另一种正则化技术,用于结合多个正则化器(L1 和 L2),以减少模型的过拟合。此外,我们还将迭代次数从 20 次减少到 15 次,以查看通过同时包含正则化和减少迭代次数是否能够提高准确度。 -
如同本章前面所做的,我们创建了一个流水线,融合了从图像生成的数值特征、
vectorizer,以及我们的逻辑回归模型logregFT。 -
然后,模型在训练数据
trainDF上进行拟合,并将模型的转换应用到测试数据testDF上。 -
我们可以再次通过交叉表对比模型的实际结果和预测结果,如下图所示:
-
现在,我们只有 1 张被错误分类的图像,而上一节中有 3 张。我们通过将
maxIter降低到15轮次,并将regParam设置为0.05,elasticNetParam设置为0.3来实现这一点。 -
我们的新准确率已经达到了
95.83%,如下图所示:
还有更多…
当然,我们的准确率从 87.5%提升到了 95.83%,仅仅通过将特定参数融入我们的模型。进一步的微调和调整参数可能会帮助我们确定是否能够达到 100%的准确率,用于图像分类模型。
参见
想了解更多关于逻辑回归中的正则化和弹性网络参数,请访问以下网站:
spark.apache.org/docs/2.2.0/mllib-linear-methods.html#logistic-regression