Spark 深度学习秘籍(二)
原文:
zh.annas-archive.org/md5/D22F0E873CEFD5D61BC00E51F025B8FB译者:飞龙
第五章:使用 Spark ML 预测消防部门呼叫
在本章中,将涵盖以下内容:
-
下载旧金山消防部门呼叫数据集
-
识别逻辑回归模型的目标变量
-
为逻辑回归模型准备特征变量
-
应用逻辑回归模型
-
评估逻辑回归模型的准确性
介绍
分类模型是预测定义的分类结果的一种流行方式。我们经常使用分类模型的输出。每当我们去电影院看电影时,我们都想知道这部电影是否被认为是正确的?数据科学社区中最流行的分类模型之一是逻辑回归。逻辑回归模型产生的响应由 S 形函数激活。S 形函数使用模型的输入并产生一个在 0 和 1 之间的输出。该输出通常以概率分数的形式呈现。许多深度学习模型也用于分类目的。通常会发现逻辑回归模型与深度学习模型一起执行,以帮助建立深度学习模型的基线。S 形激活函数是深度学习中使用的许多激活函数之一,用于产生概率输出。我们将利用 Spark 内置的机器学习库构建一个逻辑回归模型,该模型将预测旧金山消防部门的呼叫是否实际与火灾有关,而不是其他事件。
下载旧金山消防部门呼叫数据集
旧金山市在整个地区收集消防部门的服务呼叫记录做得非常好。正如他们的网站上所述,每条记录包括呼叫编号、事件编号、地址、单位标识符、呼叫类型和处理结果。包含旧金山消防部门呼叫数据的官方网站可以在以下链接找到:
data.sfgov.org/Public-Safety/Fire-Department-Calls-for-Service/nuek-vuh3
有关数据集的一些一般信息,包括列数和行数,如下截图所示:
这个当前数据集,更新于 2018 年 3 月 26 日,大约有 461 万行和 34 列。
准备工作
数据集以.csv文件的形式可供下载,并可在本地机器上下载,然后导入 Spark。
操作步骤如下:
本节将介绍下载和导入.csv文件到我们的 Jupyter 笔记本的步骤。
- 通过选择导出然后 CSV 从网站下载数据集,如下截图所示:
-
如果还没有这样做,请将下载的数据集命名为
Fire_Department_Calls_for_Service.csv -
将数据集保存到任何本地目录,尽管理想情况下应该保存到包含本章中将使用的 Spark 笔记本的相同文件夹中,如下截图所示:
- 一旦数据集已保存到与笔记本相同的目录中,执行以下
pyspark脚本将数据集导入 Spark 并创建一个名为df的数据框:
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.master("local") \
.appName("Predicting Fire Dept Calls") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
df = spark.read.format('com.databricks.spark.csv')\
.options(header='true', inferschema='true')\
.load('Fire_Department_Calls_for_Service.csv')
df.show(2)
工作原理如下:
数据集保存在与 Jupyter 笔记本相同的目录中,以便轻松导入到 Spark 会话中。
-
通过从
pyspark.sql导入SparkSession来初始化本地pyspark会话。 -
通过使用选项
header='true'和inferschema='true'读取 CSV 文件创建一个名为df的数据框。 -
最后,始终最好运行一个脚本来显示已通过数据框导入 Spark 的数据,以确认数据已传输。可以在以下截图中看到该脚本的结果,显示了来自旧金山消防局呼叫的数据集的前两行:
请注意,当我们将文件读入 spark 时,我们使用.load()将.csv文件拉入 Jupyter 笔记本。对于我们的目的来说,这是可以的,因为我们使用的是本地集群,但如果我们要利用 Hadoop 中的集群,这种方法就行不通了。
还有更多...
数据集附带有数据字典,定义了 34 列的标题。可以通过以下链接从同一网站访问此数据字典:
另请参阅
旧金山政府网站允许在线可视化数据,可用于进行一些快速数据概要分析。可以通过选择可视化下拉菜单在网站上访问可视化应用程序,如下截图所示:
识别逻辑回归模型的目标变量
逻辑回归模型作为分类算法运行,旨在预测二进制结果。在本节中,我们将指定数据集中用于预测运营商呼入电话是否与火灾或非火灾事件相关的最佳列。
准备就绪
在本节中,我们将可视化许多数据点,这将需要以下操作:
-
通过在命令行中执行
pip install matplotlib来确保安装了matplotlib。 -
运行
import matplotlib.pyplot as plt,并确保通过运行%matplotlib inline在单元格中查看图形。
此外,将对pyspark.sql中的函数进行一些操作,需要importing functions as F。
如何做...
本节将介绍如何可视化来自旧金山消防局的数据。
- 执行以下脚本以对
Call Type Group列中唯一值进行快速识别:
df.select('Call Type Group').distinct().show()
-
有五个主要类别:
-
警报。 -
潜在危及生命。 -
非危及生命。 -
火。 -
null。 -
不幸的是,其中一个类别是
null值。有必要获取每个唯一值的行计数,以确定数据集中有多少null值。执行以下脚本以生成Call Type Group列的每个唯一值的行计数:
df.groupBy('Call Type Group').count().show()
- 不幸的是,有超过 280 万行数据没有与之关联的
呼叫类型组。这超过了 460 万可用行的 60%。执行以下脚本以查看条形图中空值的不平衡情况:
df2 = df.groupBy('Call Type Group').count()
graphDF = df2.toPandas()
graphDF = graphDF.sort_values('count', ascending=False)
import matplotlib.pyplot as plt
%matplotlib inline
graphDF.plot(x='Call Type Group', y = 'count', kind='bar')
plt.title('Call Type Group by Count')
plt.show()
- 可能需要选择另一个指标来确定目标变量。相反,我们可以对
Call Type进行概要分析,以识别与火灾相关的呼叫与所有其他呼叫。执行以下脚本以对Call Type进行概要分析:
df.groupBy('Call Type').count().orderBy('count', ascending=False).show(100)
- 与
Call Type Group一样,似乎没有任何null值。Call Type有 32 个唯一类别;因此,它将被用作火灾事件的目标变量。执行以下脚本以标记包含Fire的Call Type列:
from pyspark.sql import functions as F
fireIndicator = df.select(df["Call Type"],F.when(df["Call Type"].like("%Fire%"),1)\
.otherwise(0).alias('Fire Indicator'))
fireIndicator.show()
- 执行以下脚本以检索
Fire Indicator的不同计数:
fireIndicator.groupBy('Fire Indicator').count().show()
- 执行以下脚本以将
Fire Indicator列添加到原始数据框df中:
df = df.withColumn("fireIndicator",\
F.when(df["Call Type"].like("%Fire%"),1).otherwise(0))
- 最后,将
fireIndicator列添加到数据框df中,并通过执行以下脚本进行确认:
df.printSchema()
它是如何工作的...
建立成功的逻辑回归模型的关键步骤之一是建立一个二元目标变量,该变量将用作预测结果。本节将介绍选择目标变量背后的逻辑:
- 通过识别
Call Type Group的唯一列值来执行潜在目标列的数据概要分析。我们可以查看Call Type Group列的唯一值,如下截图所示:
-
目标是确定
Call Type Group列中是否存在缺失值,以及如何处理这些缺失值。有时,可以直接删除列中的缺失值,而其他时候可以对其进行处理以填充值。 -
以下截图显示了存在多少空值:
- 此外,我们还可以绘制存在多少
null值,以更好地直观感受值的丰富程度,如下截图所示:
-
由于
Call Type Group中有超过 280 万行缺失,如df.groupBy脚本和条形图所示,删除所有这些值是没有意义的,因为这超过了数据集的总行数的 60%。因此,需要选择另一列作为目标指示器。 -
在对
Call Type列进行数据概要分析时,我们发现 32 个可能值中没有空行。这使得Call Type成为逻辑回归模型的更好目标变量候选项。以下是Call Type列的数据概要分析截图:
- 由于逻辑回归在有二元结果时效果最佳,因此使用
withColumn()操作符在df数据框中创建了一个新列,以捕获与火灾相关事件或非火灾相关事件相关的指示器(0 或 1)。新列名为fireIndicator,如下截图所示:
- 我们可以通过执行
groupBy().count()来确定火警呼叫与其他呼叫的普遍程度,如下截图所示:
- 最佳实践是通过执行新修改的数据框的
printSchema()脚本来确认新列是否已附加到现有数据框。新模式的输出如下截图所示:
还有更多...
在本节中,使用pyspark.sql模块进行了一些列操作。withColumn()操作符通过添加新列或修改同名现有列来返回新的数据框,或修改现有数据框。这与withColumnRenamed()操作符不同,后者也返回新的数据框,但是通过修改现有列的名称为新列。最后,我们需要执行一些逻辑操作,将与Fire相关的值转换为 0,没有Fire的值转换为 1。这需要使用pyspark.sql.functions模块,并将where函数作为 SQL 中 case 语句的等价物。该函数使用以下语法创建了一个 case 语句方程:
CASE WHEN Call Type LIKE %Fire% THEN 1 ELSE 0 END
新数据集的结果,Call Type和fireIndicator两列如下所示:
另请参阅
要了解更多关于 Spark 中可用的pyspark.sql模块的信息,请访问以下网站:
spark.apache.org/docs/2.2.0/api/python/pyspark.sql.html
为逻辑回归模型准备特征变量
在上一节中,我们确定了将用作逻辑回归模型预测结果的目标变量。本节将重点关注确定所有最有助于模型确定目标的特征。这被称为特征选择。
准备工作
本节将需要从pyspark.ml.feature中导入StringIndexer。为了确保正确的特征选择,我们需要将字符串列映射到索引列。这将有助于为分类变量生成不同的数值,从而为机器学习模型提供独立变量的计算便利,用于预测目标结果。
如何操作...
本节将逐步介绍为我们的模型准备特征变量的步骤。
- 执行以下脚本来更新数据框
df,只选择与任何火灾指示无关的字段:
df = df.select('fireIndicator',
'Zipcode of Incident',
'Battalion',
'Station Area',
'Box',
'Number of Alarms',
'Unit sequence in call dispatch',
'Neighborhooods - Analysis Boundaries',
'Fire Prevention District',
'Supervisor District')
df.show(5)
- 下一步是识别数据框中的任何空值并在存在时删除它们。执行以下脚本来识别具有任何空值的行数:
print('Total Rows')
df.count()
print('Rows without Null values')
df.dropna().count()
print('Row with Null Values')
df.count()-df.dropna().count()
- 有 16,551 行具有缺失值。执行以下脚本来更新数据框以删除所有具有空值的行:
df = df.dropna()
- 执行以下脚本来检索
fireIndicator的更新目标计数:
df.groupBy('fireIndicator').count().orderBy('count', ascending = False).show()
- 从
pyspark.ml.feature中导入StringIndexer类,为特征分配数值,如下脚本所示:
from pyspark.ml.feature import StringIndexer
- 使用以下脚本为模型创建所有特征变量的 Python 列表:
column_names = df.columns[1:]
- 执行以下脚本来指定输出列格式
outputcol,它将从输入列inputcol的特征列表中进行stringIndexed:
categoricalColumns = column_names
indexers = []
for categoricalCol in categoricalColumns:
stringIndexer = StringIndexer(inputCol=categoricalCol, outputCol=categoricalCol+"_Index")
indexers += [stringIndexer]
- 执行以下脚本创建一个
model,用于fit输入列并为现有数据框df生成新定义的输出列:
models = []
for model in indexers:
indexer_model = model.fit(df)
models+=[indexer_model]
for i in models:
df = i.transform(df)
- 执行以下脚本来定义数据框
df中将用于模型的特征的最终选择:
df = df.select(
'fireIndicator',
'Zipcode of Incident_Index',
'Battalion_Index',
'Station Area_Index',
'Box_Index',
'Number of Alarms_Index',
'Unit sequence in call dispatch_Index',
'Neighborhooods - Analysis Boundaries_Index',
'Fire Prevention District_Index',
'Supervisor District_Index')
工作原理...
本节将解释为我们的模型准备特征变量的步骤背后的逻辑。
- 只选择数据框中真正与火灾指示无关的指标,以贡献于预测结果的逻辑回归模型。执行此操作的原因是为了消除数据集中可能已经显示预测结果的任何潜在偏见。这最小化了人为干预最终结果。更新后的数据框的输出可以在下面的截图中看到:
请注意,列邻里-分析边界在我们提取的数据中原本拼写错误。出于连续性目的,我们将继续使用拼写错误。但是,可以使用 Spark 中的withColumnRenamed()函数来重命名列名。
- 最终选择的列如下所示:
-
火灾指示 -
事故邮政编码 -
大队 -
站点区域 -
箱 -
警报数量 -
呼叫调度中的单位序列 -
邻里-分析边界 -
消防预防区 -
监管区
-
选择这些列是为了避免我们建模中的数据泄漏。数据泄漏在建模中很常见,可能导致无效的预测模型,因为它可能包含直接由我们试图预测的结果产生的特征。理想情况下,我们希望包含真正与结果无关的特征。有几列似乎是有泄漏的,因此从我们的数据框和模型中删除了这些列。
-
识别并删除所有具有缺失或空值的行,以便在不夸大或低估关键特征的情况下获得模型的最佳性能。可以计算并显示具有缺失值的行的清单,如下脚本所示,数量为 16,551:
- 我们可以看一下与火灾相关的呼叫频率与非火灾相关的呼叫频率,如下截图所示:
- 导入
StringIndexer以帮助将几个分类或字符串特征转换为数字值,以便在逻辑回归模型中进行计算。特征的输入需要以向量或数组格式,这对于数字值是理想的。可以在以下屏幕截图中看到将在模型中使用的所有特征的列表:
- 为每个分类变量构建了一个索引器,指定了模型中将使用的输入(
inputCol)和输出(outputCol)列。数据框中的每一列都会被调整或转换,以重新构建一个具有更新索引的新输出,范围从 0 到该特定列的唯一计数的最大值。新列在末尾附加了_Index。在创建更新的列的同时,原始列仍然可在数据框中使用,如下屏幕截图所示:
- 我们可以查看其中一个新创建的列,并将其与原始列进行比较,以查看字符串是如何转换为数字类别的。以下屏幕截图显示了
Neighborhooods - Analysis Boundaries与Neighborhooods - Analysis Boundaries_Index的比较:
-
然后,数据框被修剪以仅包含数字值,并删除了转换的原始分类变量。非数字值从建模的角度来看不再有意义,并且从数据框中删除。
-
打印出新列以确认数据框的每个值类型都是双精度或整数,如下屏幕截图所示:
还有更多...
最终查看新修改的数据框将只显示数字值,如下屏幕截图所示:
另请参阅
要了解更多关于StringIndexer的信息,请访问以下网站:spark.apache.org/docs/2.2.0/ml-features.html#stringindexer。
应用逻辑回归模型
现在已经准备好将模型应用于数据框。
准备工作
本节将重点介绍一种非常常见的分类模型,称为逻辑回归,这将涉及从 Spark 中导入以下内容:
from pyspark.ml.feature import VectorAssembler
from pyspark.ml.evaluation import BinaryClassificationEvaluator
from pyspark.ml.classification import LogisticRegression
如何做...
本节将介绍应用我们的模型和评估结果步骤。
- 执行以下脚本,将数据框中的所有特征变量汇总到名为
features的列表中:
features = df.columns[1:]
- 执行以下操作以导入
VectorAssembler并配置将被分配给特征向量的字段,通过分配inputCols和outputCol:
from pyspark.ml.feature import VectorAssembler
feature_vectors = VectorAssembler(
inputCols = features,
outputCol = "features")
- 执行以下脚本,将
VectorAssembler应用于数据框,并使用transform函数:
df = feature_vectors.transform(df)
- 修改数据框,删除除
fireIndicator和features之外的所有列,如下脚本所示:
df = df.drop( 'Zipcode of Incident_Index',
'Battalion_Index',
'Station Area_Index',
'Box_Index',
'Number of Alarms_Index',
'Unit sequence in call dispatch_Index',
'Neighborhooods - Analysis Boundaries_Index',
'Fire Prevention District_Index',
'Supervisor District_Index')
- 修改数据框,将
fireIndicator重命名为label,如下脚本所示:
df = df.withColumnRenamed('fireIndicator', 'label')
- 将整个数据框
df分割为 75:25 的训练和测试集,随机种子设置为12345,如下脚本所示:
(trainDF, testDF) = df.randomSplit([0.75, 0.25], seed = 12345)
- 从
pyspark.ml.classification中导入LogisticRegression库,并配置以将数据框中的label和features合并,然后在训练数据集trainDF上拟合,如下脚本所示:
from pyspark.ml.classification import LogisticRegression
logreg = LogisticRegression(labelCol="label", featuresCol="features", maxIter=10)
LogisticRegressionModel = logreg.fit(trainDF)
- 转换测试数据框
testDF以应用逻辑回归模型。具有预测得分的新数据框称为df_predicted,如下脚本所示:
df_predicted = LogisticRegressionModel.transform(testDF)
它是如何工作的...
本节将解释应用我们的模型和评估结果步骤背后的逻辑。
-
当所有特征被合并为单个向量进行训练时,分类模型的效果最佳。因此,我们通过将所有特征收集到一个名为
features的列表中开始向量化过程。由于我们的标签是数据框的第一列,我们将其排除,并将其后的每一列作为特征列或特征变量引入。 -
向量化过程继续,将
features列表中的所有变量转换为名为features的单个向量输出到列中。此过程需要从pyspark.ml.feature导入VectorAssembler。 -
应用
VectorAssembler转换数据框,创建一个名为features的新添加列,如下截图所示:
-
在这一点上,我们在模型中需要使用的唯一列是标签列
fireIndicator和features列。数据框中的所有其他列都可以删除,因为它们在建模过程中将不再需要。 -
此外,为了帮助逻辑回归模型,我们将名为
fireIndicator的列更改为label。可以在以下截图中看到df.show()脚本的输出,其中包含新命名的列:
- 为了最小化过拟合模型,数据框将被拆分为测试和训练数据集,以在训练数据集
trainDF上拟合模型,并在测试数据集testDF上进行测试。设置随机种子为12345,以确保每次执行单元格时随机性保持一致。可以在以下截图中看到数据拆分的行数:
-
然后,从
pyspark.ml.classification导入逻辑回归模型LogisticRegression,并配置以从与特征和标签相关的数据框中输入适当的列名。此外,逻辑回归模型分配给一个名为logreg的变量,然后拟合以训练我们的数据集trainDF。 -
基于测试数据框
testDF的转换,创建一个名为predicted_df的新数据框,一旦逻辑回归模型对其进行评分。该模型为predicted_df创建了三个额外的列,基于评分。这三个额外的列是rawPrediction、probability和prediction,如下截图所示:
- 最后,可以对
df_predicted中的新列进行概要,如下截图所示:
还有更多...
需要牢记的一件重要事情是,因为它可能最初看起来有些违反直觉,我们的概率阈值在数据框中设置为 50%。任何概率为 0.500 及以上的呼叫都会被预测为 0.0,任何概率小于 0.500 的呼叫都会被预测为 1.0。这是在管道开发过程中设置的,只要我们知道阈值是多少以及如何分配预测,我们就没问题。
另请参阅
要了解有关VectorAssembler的更多信息,请访问以下网站:
spark.apache.org/docs/latest/ml-features.html#vectorassembler
评估逻辑回归模型的准确性
现在我们准备好评估预测呼叫是否被正确分类为火灾事件的性能。
准备工作
我们将执行模型分析,需要导入以下内容:
from sklearn import metrics
如何做...
本节将逐步介绍评估模型性能的步骤。
- 使用
.crosstab()函数创建混淆矩阵,如下脚本所示:
df_predicted.crosstab('label', 'prediction').show()
- 从
sklearn导入metrics以帮助使用以下脚本衡量准确性:
from sklearn import metrics
- 为了衡量准确性,从数据框中创建
actual和predicted列的两个变量,使用以下脚本:
actual = df_predicted.select('label').toPandas()
predicted = df_predicted.select('prediction').toPandas()
- 使用以下脚本计算准确度预测分数:
metrics.accuracy_score(actual, predicted)
它是如何工作的...
本节解释了如何评估模型性能。
- 为了计算我们模型的准确度,重要的是能够确定我们的预测有多准确。通常,最好使用混淆矩阵交叉表来可视化,显示正确和错误的预测分数。我们使用
df_predicted数据框的crosstab()函数创建一个混淆矩阵,它显示我们对标签为 0 的有 964,980 个真负预测,对标签为 1 的有 48,034 个真正预测,如下截图所示:
-
我们从本节前面知道
testDF数据框中共有 1,145,589 行;因此,我们可以使用以下公式计算模型的准确度:(TP + TN) / 总数。准确度为 88.4%。 -
需要注意的是,并非所有的假分数都是相等的。例如,将一个呼叫分类为与火灾无关,最终却与火灾有关,比相反的情况对火灾安全的影响更大。这被称为假阴性。有一个考虑假阴性(FN)的指标,称为召回率。
-
虽然我们可以手动计算准确度,如最后一步所示,但最好是自动计算准确度。这可以通过导入
sklearn.metrics来轻松实现,这是一个常用于评分和模型评估的模块。 -
sklearn.metrics接受两个参数,我们拥有标签的实际结果和从逻辑回归模型中得出的预测值。因此,创建了两个变量actual和predicted,并使用accuracy_score()函数计算准确度分数,如下截图所示:
- 准确度分数与我们手动计算的相同,为 88.4%。
还有更多...
现在我们知道我们的模型能够准确预测呼叫是否与火灾相关的比率为 88.4%。起初,这可能听起来是一个强有力的预测;然而,将其与一个基准分数进行比较总是很重要,其中每个呼叫都被预测为非火灾呼叫。预测的数据框df_predicted中标签1和0的分布如下截图所示:
我们可以对同一数据框运行一些统计,使用df_predicted.describe('label').show()脚本得到值为1的标签出现的平均值。该脚本的输出如下截图所示:
基础模型的预测值为1的比率为 14.94%,换句话说,它对值为 0 的预测率为100 - 14.94%,即 85.06%。因此,由于 85.06%小于模型的预测率 88.4%,这个模型相比于盲目猜测呼叫是否与火灾相关提供了改进。
另请参阅
要了解更多关于准确度与精确度的信息,请访问以下网站:
www.mathsisfun.com/accuracy-precision.html
第六章:在生成网络中使用 LSTMs
阅读完本章后,您将能够完成以下任务:
-
下载将用作输入文本的小说/书籍
-
准备和清理数据
-
对句子进行标记化
-
训练并保存 LSTM 模型
-
使用模型生成类似的文本
介绍
由于循环神经网络(RNNs)在反向传播时存在一些缺点,长短期记忆单元(LSTMs)和门控循环单元(GRUs)在学习顺序输入数据时近来变得越来越受欢迎,因为它们更适合解决梯度消失和梯度爆炸的问题。
下载将用作输入文本的小说/书籍
在本示例中,我们将介绍下载小说/书籍所需的步骤,这些将作为本示例的输入文本进行执行。
准备工作
-
将输入数据以
.txt文件的形式放在工作目录中。 -
输入可以是任何类型的文本,如歌词、小说、杂志文章和源代码。
-
大多数经典文本不再受版权保护,可以免费下载并用于实验。获取免费书籍的最佳途径是 Project Gutenberg。
-
在本章中,我们将使用 Rudyard Kipling 的《丛林之书》作为输入来训练我们的模型,并生成统计上类似的文本作为输出。下面的截图显示了如何以
.txt格式下载必要的文件:
- 访问网站并搜索所需的书籍后,点击“Plain Text UTF-8”并下载。UTF-8 基本上指定了编码的类型。可以通过点击链接将文本复制粘贴或直接保存到工作目录中。
操作步骤...
在开始之前,先看一下数据并进行分析总是有帮助的。查看数据后,我们可以看到有很多标点符号、空格、引号以及大写和小写字母。在对其进行任何分析或将其馈送到 LSTM 网络之前,我们需要先准备好数据。我们需要一些能够更轻松处理数据的库:
- 通过以下命令导入必要的库:
from keras.preprocessing.text import Tokenizer
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense, lSTM, Dropout, Embedding
import numpy as np
from pickle import dump
import string
- 前面命令的输出如下截屏所示:
- 始终要仔细检查当前工作目录,并选择所需的文件夹作为工作目录。在我们的案例中,
.txt文件名为junglebook.txt,保存在名为Chapter 8的文件夹中。因此,我们将选择该文件夹作为整个章节的工作目录。可以按照下面的截图所示进行操作:
- 接下来,通过定义一个名为
load_document的函数将文件加载到程序的内存中,可以通过以下命令完成:
def load_document(name):
file = open(name, 'r')
text = file.read()
file.close()
return text
- 使用先前定义的函数将文档加载到内存中,并使用以下脚本打印文本文件的前 2000 个字符:
input_filename = 'junglebook.txt'
doc = load_document(input_filename)
print(doc[:2000])
- 运行前述函数以及命令会产生如下截屏所示的输出:
上述代码的输出如下截屏所示:
下面的截图是前面输出的延续:
- 如前面的截图所示,打印了
.txt文件中的前 2000 个字符。在执行任何预处理之前,始终先分析数据是个好主意。这将更好地指导我们如何进行预处理步骤。
工作原理...
-
array函数将用于处理数组形式的数据。numpy库提供了这个函数。 -
由于我们的数据只是文本数据,我们将需要字符串库来处理所有输入数据作为字符串,然后将单词编码为整数,以便进行馈送。
-
tokenizer函数将用于将所有句子拆分为标记,其中每个标记代表一个单词。 -
pickle 库将被需要,以便使用
dump函数将字典保存到 pickle 文件中。 -
keras库中的to_categorical函数将类向量(整数)转换为二进制类矩阵,例如,用于categorical_crossentropy,我们以后将需要将标记映射到唯一整数,反之亦然。 -
本章中所需的其他 Keras 层包括 LSTM 层、密集层、dropout 层和嵌入层。模型将被顺序定义,因此我们需要
keras库中的顺序模型。
还有更多...
-
您还可以使用相同的模型处理不同类型的文本,例如网站上的客户评论、推文、结构化文本(如源代码、数学理论等)等。
-
本章的目的是了解 LSTM 如何学习长期依赖关系,以及与循环神经网络相比,它们在处理序列数据时表现更好的方式。
-
另一个好主意是将* Pokémon 名称输入模型,并尝试生成自己的 Pokémon *名称。
另请参阅
有关使用的不同库的更多信息可以在以下链接找到:
准备和清理数据
本章的这一部分将讨论在将其作为输入馈送到模型之前涉及的各种数据准备和文本预处理步骤。我们准备数据的具体方式取决于我们打算对其进行建模的方式,这又取决于我们打算如何使用它。
准备工作
语言模型将基于统计数据,并预测给定文本输入序列的每个单词的概率。预测的单词将被馈送到模型中,以便生成下一个单词。
一个关键决定是输入序列应该有多长。它们需要足够长,以使模型能够学习单词的上下文以进行预测。此输入长度还将定义用于生成新序列的种子文本的长度,当我们使用模型时。
为了简单起见,我们将任意选择长度为 50 个单词的输入序列长度。
如何做...
根据对文本的审查(我们之前做过),以下是可以执行的一些操作,以清理和预处理输入文件中的文本。我们提出了一些关于文本预处理的选项。但是,您可能希望探索更多的清理操作作为练习:
-
用空格替换破折号
–,以便更好地拆分单词 -
基于空格拆分单词
-
删除输入文本中的所有标点符号,以减少输入模型的文本中唯一字符的数量(例如,Why? 变为 Why)
-
删除所有非字母的单词,以删除独立的标点符号标记和表情符号
-
将所有单词从大写转换为小写,以进一步减少标记的总数并消除任何差异和数据冗余
词汇量是语言建模和决定模型训练时间的决定性因素。较小的词汇量会导致训练速度更快的更高效的模型。在某些情况下,拥有较小的词汇量是有益的,但在其他情况下,拥有较大的词汇量可以防止过拟合。为了预处理数据,我们需要一个函数,它接受整个输入文本,根据空格分割文本,删除所有标点,规范化所有情况,并返回一个标记序列。为此,通过以下命令定义clean_document函数:
import string
def clean_document(doc):
doc = doc.replace('--', ' ')
tokens = doc.split()
table = str.maketrans('', '', string.punctuation)
tokens = [w.translate(table) for w in tokens]
tokens = [word for word in tokens if word.isalpha()]
tokens = [word.lower() for word in tokens]
return tokens
- 先前定义的函数基本上会将加载的文档/文件作为其参数,并返回一个干净的标记数组,如下面的屏幕截图所示:
- 接下来,打印出一些标记和统计数据,以更好地了解
clean_document函数的作用。通过以下命令完成此步骤:
tokens = clean_document(doc)
print(tokens[:200])
print('Total Tokens: %d' % len(tokens))
print('Total Unique Tokens: %d' % len(set(tokens)))
- 上述一系列命令的输出打印了前两百个标记,如下面的屏幕截图所示:
- 接下来,使用以下命令将所有这些标记组织成序列,每个序列包含 50 个单词(任意选择):
length = 50 + 1
sequences = list()
for i in range(length, len(tokens)):
seq = tokens[i-sequence_length:i]
line = ' '.join(seq)
sequences.append(line)
print('Total Sequences: %d' % len(sequences))
可以通过打印输出文档形成的序列的总数来查看,如下面的屏幕截图所示:
- 通过以下命令定义
save_doc函数,将所有生成的标记以及序列保存到工作目录中的文件中:
def save_document(lines, name):
data = '\n'.join(lines)
file = open(name, 'w')
file.write(data)
file.close()
要保存这些序列,请使用以下两个命令:
output_filename = 'junglebook_sequences.txt'
save_document(sequences, output_filename)
- 该过程如下屏幕截图所示:
- 接下来,加载保存的文档,其中包含所有保存的标记和序列,到内存中使用定义如下的
load_document函数:
def load_document(name):
file = open(name, 'r')
text = file.read()
file.close()
return text
# function to load document and split based on lines
input_filename = 'junglebook_sequences.txt'
doc = load_document(input_filename)
lines = doc.split('\n')
工作原理...
-
clean_document函数删除所有空格、标点、大写文本和引号,并将整个文档分割成标记,其中每个标记都是一个单词。 -
通过打印文档中的标记总数和唯一标记总数,我们会注意到
clean_document函数生成了 51,473 个标记,其中 5,027 个标记(或单词)是唯一的。 -
然后,
save_document函数保存所有这些标记,以及生成我们每个 50 个单词的序列所需的唯一标记。请注意,通过循环遍历所有生成的标记,我们能够生成一个包含 51,422 个序列的长列表。这些序列将用作训练语言模型的输入。 -
在对所有 51,422 个序列进行模型训练之前,将标记以及序列保存到文件中始终是一个良好的做法。一旦保存,可以使用定义的
load_document函数将文件加载回内存。 -
这些序列组织为 50 个输入标记和一个输出标记(这意味着每个序列有 51 个标记)。为了预测每个输出标记,将使用前 50 个标记作为模型的输入。我们可以通过迭代从第 51 个标记开始的标记列表,并将前 50 个标记作为一个序列,然后重复此过程直到所有标记列表的末尾来实现这一点。
另请参阅
访问以下链接,以更好地了解使用各种函数进行数据准备:
对句子进行标记
在定义和输入数据到 LSTM 网络之前,重要的是将数据转换为神经网络可以理解的形式。计算机理解的一切都是二进制代码(0 和 1),因此,文本或字符串格式的数据需要转换为独热编码变量。
准备工作
要了解独热编码的工作原理,请访问以下链接:
-
machinelearningmastery.com/how-to-one-hot-encode-sequence-data-in-python/ -
scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html -
stackoverflow.com/questions/37292872/how-can-i-one-hot-encode-in-python -
hackernoon.com/what-is-one-hot-encoding-why-and-when-do-you-have-to-use-it-e3c6186d008f
如何做...
经过上一节的学习,您应该能够清理整个语料库并拆分句子。接下来涉及独热编码和标记化句子的步骤可以按以下方式完成:
-
一旦标记和序列被保存到文件并加载到内存中,它们必须被编码为整数,因为模型中的词嵌入层期望输入序列由整数而不是字符串组成。
-
这是通过将词汇表中的每个单词映射到唯一的整数并对输入序列进行编码来完成的。稍后,在进行预测时,可以将预测转换(或映射)回数字,以查找它们在相同映射中关联的单词,并从整数到单词的反向映射。
-
为了执行这种编码,利用 Keras API 中的 Tokenizer 类。在编码之前,必须对整个数据集进行训练,以便找到所有唯一的标记,并为每个标记分配一个唯一的整数。要这样做的命令如下:
tokenizer = Tokenizer()
tokenizer.fit_on_texts(lines)
sequences = tokenizer.texts_to_sequences(lines)
-
在后面定义嵌入层之前,还需要计算词汇表的大小。这是通过计算映射字典的大小来确定的。
-
因此,在向嵌入层指定词汇表大小时,将其指定为实际词汇表大小加 1。因此,词汇表大小定义如下:
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary size : %d' % vocab_size)
-
现在,一旦输入序列已经被编码,它们需要被分成输入和输出元素,这可以通过数组切片来完成。
-
分离后,对输出单词进行独热编码。这意味着将其从整数转换为 n 维向量,其中每个词汇表中的单词都有一个 0 值,用 1 表示单词的整数值的索引处的特定单词。Keras 提供了
to_categorical()函数,可用于为每个输入-输出序列对独热编码输出单词。 -
最后,指定嵌入层输入序列的长度。我们知道有 50 个单词,因为模型是通过将序列长度指定为 50 来设计的,但指定序列长度的一个好的通用方法是使用输入数据形状的第二维(列数)。
-
可以通过发出以下命令来完成:
sequences = array(sequences)
Input, Output = sequences[:,:-1], sequences[:,-1]
Output = to_categorical(Output, num_classes=vocab_size)
sequence_length = Input.shape[1]
工作原理...
本节将描述在执行上一节中的命令时必须看到的输出:
- 在对句子进行标记化和计算词汇表长度的命令运行后,您应该看到如下屏幕截图所示的输出:
-
单词被分配值,从 1 开始,直到单词的总数(例如,在这种情况下为 5,027)。嵌入层需要为词汇表中从索引 1 到最大索引的每个单词分配一个向量表示。词汇表末尾的单词的索引将是 5,027;这意味着数组的长度必须是 5,027 + 1。
-
数组切片和将句子分隔成每个序列 50 个单词的序列后,输出应该如下截图所示:
- 使用
to_categorical()函数,使模型学习预测下一个单词的概率分布。
还有更多...
有关在 Python 中重新整形数组的更多信息,请访问以下链接:
-
docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html -
machinelearningmastery.com/index-slice-reshape-numpy-arrays-machine-learning-python/
训练和保存 LSTM 模型
现在可以从准备好的数据中训练统计语言模型。
将要训练的模型是神经语言模型。它具有一些独特的特点:
-
它使用分布式表示来表示单词,使得具有相似含义的不同单词具有相似的表示
-
它在学习模型的同时学习表示
-
它学会使用前 50 个单词的上下文来预测下一个单词的概率
具体来说,您将使用嵌入层来学习单词的表示,以及长短期记忆(LSTM)递归神经网络来学习根据上下文预测单词。
准备工作
如前所述,学习的嵌入需要知道词汇表的大小和输入序列的长度。它还有一个参数,用于指定将用于表示每个单词的维度的数量。这就是嵌入向量空间的大小。
常见值为 50、100 和 300。我们将在这里使用 100,但考虑测试更小或更大的值,并评估这些值的指标。
网络将由以下组成:
-
两个具有 200 个记忆单元的 LSTM 隐藏层。更多的记忆单元和更深的网络可能会取得更好的结果。
-
一个 dropout 层,dropout 率为 0.3 或 30%,这将帮助网络减少对每个神经元/单元的依赖,并减少过拟合数据。
-
一个具有 200 个神经元的全连接层连接到 LSTM 隐藏层,以解释从序列中提取的特征。
-
输出层预测下一个单词,作为词汇表大小的单个向量,其中每个单词在词汇表中都有一个概率。
-
在第二个密集或全连接层中使用 softmax 分类器,以确保输出具有归一化概率的特性(例如在 0 和 1 之间)。
如何做...
- 使用以下命令定义模型,并在以下截图中进行说明:
model = Sequential()
model.add(Embedding(vocab_size, 100, input_length=sequence_length))
model.add(LSTM(200, return_sequences=True))
model.add(LSTM(200))
model.add(Dropout(0.3))
model.add(Dense(200, activation='relu'))
model.add(Dense(vocab_size, activation='softmax'))
print(model.summary())
-
打印模型摘要,以确保模型按预期构建。
-
编译模型,指定需要拟合模型的分类交叉熵损失。将 epochs 数设置为 75,并使用批量大小为 250 的小批量训练模型。使用以下命令完成:
model.compile(loss='categorical_crossentropy', optimizer='adam',
metrics=['accuracy'])
model.fit(Input, Output, batch_size=250, epochs=75)
- 上述命令的输出在以下截图中进行说明:
- 模型编译完成后,使用以下命令保存:
model.save('junglebook_trained.h5')
dump(tokenizer, open('tokenizer.pkl', 'wb'))
它是如何工作的...
-
模型是使用 Keras 框架中的
Sequential()函数构建的。模型中的第一层是一个嵌入层,它以词汇量、向量维度和输入序列长度作为参数。 -
接下来的两层是每个具有 200 个内存单元的 LSTM 层。可以尝试使用更多内存单元和更深的网络来检查是否可以提高准确性。
-
接下来的一层是一个丢弃层,丢弃概率为 30%,这意味着在训练过程中某个记忆单元不被使用的概率为 30%。这可以防止数据过拟合。同样,可以调整和调优丢弃概率。
-
最后两层是两个全连接层。第一个具有
relu激活函数,第二个具有 softmax 分类器。打印模型摘要以检查模型是否按要求构建。 -
请注意,在这种情况下,可训练参数的总数为 2,115,228。模型摘要还显示了模型中每个层将被训练的参数数量。
-
在我们的案例中,模型是在 75 个时期的小批量中训练的,以最小化训练时间。将时期数增加到 100 以上,并在训练时使用更小的批量,可以大大提高模型的准确性,同时减少损失。
-
在训练过程中,您将看到性能摘要,包括每个批次更新结束时从训练数据评估的损失和准确性。在我们的案例中,运行了 75 个时期后,我们获得了接近 40%的准确性。
-
模型的目标不是以 100%的准确性记住文本,而是捕捉输入文本的属性,如自然语言和句子中存在的长期依赖关系和结构。
-
在训练完成后,模型将保存在名为
junglebook_trained.h5的工作目录中。 -
当模型稍后加载到内存中进行预测时,我们还需要单词到整数的映射。这在
Tokenizer对象中存在,并且也使用Pickle库中的dump()函数保存。
还有更多...
Jason Brownlee 在 Machine Learning Mastery 的博客上有很多关于开发、训练和调整自然语言处理机器学习模型的有用信息。可以在以下链接找到:
machinelearningmastery.com/deep-learning-for-nlp/
machinelearningmastery.com/lstms-with-python/
machinelearningmastery.com/blog/
另请参阅
有关本节中使用的不同 keras 层和其他函数的更多信息可以在以下链接找到:
使用模型生成类似的文本
现在您有了一个经过训练的语言模型,可以使用它。在这种情况下,您可以使用它来生成具有与源文本相同统计特性的新文本序列。至少对于这个例子来说,这并不实际,但它给出了语言模型学到了什么的一个具体例子。
准备工作
- 首先重新加载训练序列。您可以使用我们最初开发的
load_document()函数来实现。通过以下代码实现:
def load_document(name):
file = open(name, 'r')
text = file.read()
file.close()
return text
# load sequences of cleaned text
input_filename = 'junglebook_sequences.txt'
doc = load_document(input_filename)
lines = doc.split('\n')
上述代码的输出如下截图所示:
-
请注意,输入文件名现在是
'junglebook_sequences.txt',这将把保存的训练序列加载到内存中。我们需要文本,以便我们可以选择一个源序列作为模型的输入,以生成新的文本序列。 -
模型将需要 50 个单词作为输入。
随后,需要指定输入的预期长度。这可以通过计算加载的数据的一行的长度并减去 1 来从输入序列中确定,因为预期的输出单词也在同一行上,如下所示:
sequence_length = len(lines[0].split()) - 1
- 接下来,通过执行以下命令将训练和保存的模型加载到内存中:
from keras.models import load_model
model = load_model('junglebook.h5')
- 生成文本的第一步是准备种子输入。为此目的,从输入文本中随机选择一行文本。一旦选择,打印它以便您对使用的内容有一些了解。操作如下:
from random import randint
seed_text = lines[randint(0,len(lines))]
print(seed_text + '\n')
如何做...
- 现在,您可以逐个生成新单词。首先,使用训练模型时使用的相同标记器将种子文本编码为整数,操作如下:
encoded = tokenizer.texts_to_sequences([seed_text])[0]
- 模型可以通过调用
model.predict_classes()直接预测下一个单词,这将返回具有最高概率的单词的索引:
prediction = model.predict_classes(encoded, verbose=0)
- 查找标记器映射中的索引以获取相关联的单词,如下所示:
out_word = ''
for word, index in tokenizer.word_index.items():
if index == prediction:
out_word = word
break
- 将这个单词附加到种子文本中并重复这个过程。重要的是,输入序列将变得太长。在将输入序列编码为整数后,我们可以将其截断为所需的长度。Keras 提供了
pad_sequences()函数,我们可以使用它来执行这种截断,如下所示:
encoded = pad_sequences([encoded], maxlen=seq_length, truncating='pre')
- 将所有这些封装到一个名为
generate_sequence()的函数中,该函数以模型、标记器、输入序列长度、种子文本和要生成的单词数量作为输入。然后,它返回模型生成的一系列单词。您可以使用以下代码来实现:
from random import randint
from pickle import load
from keras.models import load_model
from keras.preprocessing.sequence import pad_sequences
def load_document(filename):
file = open(filename, 'r')
text = file.read()
file.close()
return text
def generate_sequence(model, tokenizer, sequence_length, seed_text, n_words):
result = list()
input_text = seed_text
for _ in range(n_words):
encoded = tokenizer.texts_to_sequences([input_text])[0]
encoded = pad_sequences([encoded], maxlen=seq_length, truncating='pre')
prediction = model.predict_classes(encoded, verbose=0)
out_word = ''
for word, index in tokenizer.word_index.items():
if index == prediction:
out_word = word
break
input_text += ' ' + out_word
result.append(out_word)
return ' '.join(result)
input_filename = 'junglebook_sequences.txt'
doc = load_document(input_filename)
lines = doc.split('\n')
seq_length = len(lines[0].split()) - 1
工作原理...
现在,我们准备生成一系列新单词,假设我们有一些种子文本:
- 首先使用以下命令将模型重新加载到内存中:
model = load_model('junglebook.h5')
- 接下来,通过输入以下命令加载标记器:
tokenizer = load(open('tokenizer.pkl', 'rb'))
- 通过使用以下命令随机选择一个种子文本:
seed_text = lines[randint(0,len(lines))]
print(seed_text + '\n')
- 最后,通过使用以下命令生成一个新序列:
generated = generate_sequence(model, tokenizer, sequence_length, seed_text, 50)
print(generated)
- 在打印生成的序列时,您将看到类似于以下屏幕截图的输出:
- 模型首先打印随机种子文本的 50 个单词,然后打印生成文本的 50 个单词。在这种情况下,随机种子文本如下:
篮子里装满了干草,放入蚱蜢,或者捉两只螳螂让它们打架,或者串一串红色和黑色的丛林果仁做成项链,或者看蜥蜴在岩石上晒太阳,或者蛇在泥坑附近捕捉青蛙,然后它们唱着长长的歌
在这种情况下,模型生成的 50 个单词如下:
在评论结束时有奇怪的本地颤音,他看到的鬣狗,他们感到被拉到他周围的噪音,为了峡谷末端的画面,嗅着被咬的和最好的公牛在黎明时是本地人
- 请注意模型输出了一系列随机单词,这些单词是根据它从输入文本中学到的内容生成的。您还会注意到,模型在模仿输入文本并生成自己的故事方面做得相当不错。尽管文本没有太多意义,但它为我们提供了宝贵的见解,即模型如何学习将统计上相似的单词放在一起。
还有更多...
-
更改设置的随机种子后,网络生成的输出也会发生变化。您可能无法获得与前面示例完全相同的输出文本,但它将与用于训练模型的输入非常相似。
-
以下是通过多次运行生成文本片段获得的不同结果的一些屏幕截图:
- 模型甚至生成了自己版本的项目古腾堡许可证,如下屏幕截图所示:
-
模型的准确性可以通过将时代数量从大约 100 增加到 200 来提高到约 60%。另一种增加学习的方法是通过以大约 50 和 100 的小批量训练模型。尝试玩弄不同的超参数和激活函数,以查看以最佳方式影响结果的方法。
-
还可以通过在定义模型时包含更多的 LSTM 和丢失层来使模型更加密集。但是,请注意,如果模型更复杂并且运行的时代更长,它只会增加训练时间。
-
经过大量实验,发现理想的批处理大小在 50 到 100 之间,并且确定了训练模型的理想时代数量在 100 到 200 之间。
-
执行前述任务的确切方法并不存在。您还可以尝试使用不同的文本输入到模型,例如推文,客户评论或 HTML 代码。
-
还可以执行一些其他任务,包括使用简化的词汇表(例如删除所有停用词)以进一步增强字典中的唯一单词;调整嵌入层的大小和隐藏层中的记忆单元数量;并扩展模型以使用预训练模型,例如 Google 的 Word2Vec(预训练词模型),以查看是否会产生更好的模型。
另请参阅
有关本章最后一节中使用的各种函数和库的更多信息,请访问以下链接:
第七章:使用 TF-IDF 进行自然语言处理
在本章中,将涵盖以下内容:
-
下载治疗机器人会话文本数据集
-
分析治疗机器人会话数据集
-
可视化数据集中的词频
-
计算文本的情感分析
-
从文本中删除停用词
-
训练 TF-IDF 模型
-
评估 TF-IDF 模型性能
-
将模型性能与基准分数进行比较
介绍
自然语言处理(NLP)最近成为新闻的焦点,如果你问五个不同的人,你会得到十个不同的定义。最近,NLP 已被用于帮助识别互联网上试图传播假新闻或更糟的是欺凌行为的机器人或喷子。事实上,最近在西班牙发生了一起案件,一所学校的学生通过社交媒体账户遭到网络欺凌,这对学生的健康产生了严重影响,老师们开始介入。学校联系了研究人员,他们能够帮助识别使用 TF-IDF 等 NLP 方法的潜在喷子。最终,潜在的学生名单被提交给学校,当面对时,实际嫌疑人承认了自己的行为。这个故事发表在一篇名为《Twitter 社交网络中喷子档案检测的监督机器学习:网络欺凌的真实案例应用》的论文中,作者是 Patxi Galan-Garcıa、Jose Gaviria de la Puerta、Carlos Laorden Gomez、Igor Santos 和 Pablo Garcıa Bringas。
本文重点介绍了利用多种不同方法分析文本和开发类似人类语言处理的能力。正是这种方法将自然语言处理(NLP)融入到机器学习、深度学习和人工智能中。让机器能够摄取文本数据并可能从同样的文本数据中做出决策是自然语言处理的核心。有许多用于 NLP 的算法,例如以下内容:
-
TF-IDF
-
Word2Vec
-
N-gram
-
潜在狄利克雷分配(LDA)
-
长短期记忆(LSTM)
本章将专注于一个包含个人与在线治疗网站聊天机器人之间对话的数据集。聊天机器人的目的是识别需要立即引起个人关注而不是继续与聊天机器人讨论的对话。最终,我们将专注于使用 TF-IDF 算法对数据集进行文本分析,以确定聊天对话是否需要被升级到个人的分类。TF-IDF 代表词项频率-逆文档频率。这是一种常用的算法技术,用于识别文档中单词的重要性。此外,TF-IDF 在处理文档中的高词频时易于计算,并且能够衡量单词的独特性。在处理聊天机器人数据时,这非常有用。主要目标是快速识别一个唯一的单词,触发升级到个人以提供即时支持。
下载治疗机器人会话文本数据集
本节将重点介绍下载和设置本章中用于 NLP 的数据集。
准备工作
本章将使用基于治疗机器人与在线治疗网站访客之间的互动的数据集。它包含 100 个互动,每个互动都被标记为“升级”或“不升级”。如果讨论需要更严肃的对话,机器人将会将讨论标记为“升级”给个人。否则,机器人将继续与用户讨论。
它是如何工作的...
本节将介绍下载聊天机器人数据的步骤。
-
从以下 GitHub 存储库访问数据集:
github.com/asherif844/ApacheSparkDeepLearningCookbook/tree/master/CH07/data -
一旦您到达存储库,右键单击以下截图中看到的文件:
-
下载
TherapyBotSession.csv并保存到与 Jupyter 笔记本SparkSession相同的本地目录中。 -
通过以下脚本在 Jupyter 笔记本中访问数据集,构建名为
spark的SparkSession,并将数据集分配给 Spark 中的数据框df:
spark = SparkSession.builder \
.master("local") \
.appName("Natural Language Processing") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
df = spark.read.format('com.databricks.spark.csv')\
.options(header='true', inferschema='true')\
.load('TherapyBotSession.csv')
如何做...
本节解释了聊天机器人数据如何进入我们的 Jupyter 笔记本。
- 数据集的内容可以通过点击存储库中的 TherapyBotSession.csv 查看,如下截图所示:
- 一旦数据集被下载,它可以被上传并转换为一个名为
df的数据框。可以通过执行df.show()来查看数据框,如下截图所示:
-
有 3 个主要字段对我们来说特别感兴趣:
-
id:网站访问者和聊天机器人之间每笔交易的唯一标识。 -
label:由于这是一种监督建模方法,我们知道我们要预测的结果,每个交易都被分类为escalate或do_not_escalate。在建模过程中,将使用该字段来训练文本以识别属于这两种情况之一的单词。 -
chat:最后我们有来自网站访问者的chat文本,我们的模型将对其进行分类。
还有更多...
数据框 df 还有一些额外的列 _c3、_c4、_c5 和 _c6,这些列将不会在模型中使用,因此可以使用以下脚本从数据集中排除。
df = df.select('id', 'label', 'chat')
df.show()
脚本的输出可以在以下截图中看到:
分析治疗机器人会话数据
在应用模型之前,始终先分析任何数据集是很重要的
准备工作
这一部分将需要从 pyspark.sql 导入 functions 来在我们的数据框上执行。
import pyspark.sql.functions as F
如何做...
以下部分将介绍对文本数据进行分析的步骤。
- 执行以下脚本来对
label列进行分组并生成计数分布:
df.groupBy("label") \
.count() \
.orderBy("count", ascending = False) \
.show()
- 使用以下脚本向数据框
df添加一个新列word_count:
import pyspark.sql.functions as F
df = df.withColumn('word_count', F.size(F.split(F.col('response_text'),' ')))
- 使用以下脚本按
label聚合平均单词计数avg_word_count:
df.groupBy('label')\
.agg(F.avg('word_count').alias('avg_word_count'))\
.orderBy('avg_word_count', ascending = False) \
.show()
它是如何工作的...
以下部分解释了分析文本数据所获得的反馈。
- 收集跨多行的数据并按维度对结果进行分组是很有用的。在这种情况下,维度是
label。使用df.groupby()函数来测量按label分布的 100 笔在线治疗交易的计数。我们可以看到do_not_escalate到escalate的分布是65:35,如下截图所示:
- 创建一个新列
word_count,用于计算聊天机器人和在线访问者之间的 100 笔交易中每笔交易使用了多少单词。新创建的列word_count可以在以下截图中看到:
- 由于现在在数据框中添加了
word_count,可以对其进行聚合以计算按label的平均单词计数。一旦执行了这个操作,我们可以看到escalate对话的平均长度是do_not_escalate对话的两倍多,如下截图所示:
可视化数据集中的单词计数
一张图片胜过千言万语,本节将证明这一点。不幸的是,截至版本 2.2,Spark 没有任何内在的绘图能力。为了在数据框中绘制值,我们必须转换为 pandas。
准备工作
本节将需要导入matplotlib进行绘图:
import matplotlib.pyplot as plt
%matplotlib inline
如何做...
本节将介绍将 Spark 数据框转换为可以在 Jupyter 笔记本中查看的可视化的步骤。
- 使用以下脚本将 Spark 数据框转换为
pandas数据框:
df_plot = df.select('id', 'word_count').toPandas()
- 使用以下脚本绘制数据框:
import matplotlib.pyplot as plt
%matplotlib inline
df_plot.set_index('id', inplace=True)
df_plot.plot(kind='bar', figsize=(16, 6))
plt.ylabel('Word Count')
plt.title('Word Count distribution')
plt.show()
工作原理...
本节解释了如何将 Spark 数据框转换为pandas,然后绘制。
-
从 Spark 中收集数据框的子集,并使用 Spark 中的
toPandas()方法转换为pandas。 -
然后使用 matplotlib 绘制数据的子集,将 y 值设置为
word_count,将 x 值设置为id,如下面的屏幕截图所示:
另请参阅
Python 中除了matplotlib之外还有其他绘图功能,例如bokeh、plotly和seaborn。
要了解有关bokeh的更多信息,请访问以下网站:
要了解有关plotly的更多信息,请访问以下网站:
要了解有关seaborn的更多信息,请访问以下网站:
计算文本的情感分析
情感分析是从单词或一系列单词中推导出语气和感觉的能力。本节将利用 Python 技术从数据集中的 100 个交易中计算情感分析分数。
准备工作
本节将需要在 PySpark 中使用函数和数据类型。此外,我们还将导入TextBlob库进行情感分析。为了在 PySpark 中使用 SQL 和数据类型函数,必须导入以下内容:
from pyspark.sql.types import FloatType
此外,为了使用TextBlob,必须导入以下库:
from textblob import TextBlob
如何做...
以下部分将介绍将情感分数应用于数据集的步骤。
- 使用以下脚本创建情感分数函数
sentiment_score:
from textblob import TextBlob
def sentiment_score(chat):
return TextBlob(chat).sentiment.polarity
-
使用以下脚本在数据框中的每个对话响应上应用
sentiment_score: -
创建一个名为
sentiment_score_udf的lambda函数,将sentiment_score映射到 Spark 中的用户定义函数udf,并指定FloatType()的输出类型,如下脚本所示:
from pyspark.sql.types import FloatType
sentiment_score_udf = F.udf(lambda x: sentiment_score(x), FloatType())
- 在数据框中的每个
chat列上应用函数sentiment_score_udf,如下脚本所示:
df = df.select('id', 'label', 'chat','word_count',
sentiment_score_udf('chat').alias('sentiment_score'))
- 使用以下脚本计算按
label分组的平均情感分数avg_sentiment_score:
df.groupBy('label')\
.agg(F.avg('sentiment_score').alias('avg_sentiment_score'))\
.orderBy('avg_sentiment_score', ascending = False) \
.show()
工作原理...
本节解释了如何将 Python 函数转换为 Spark 中的用户定义函数udf,以将情感分析分数应用于数据框中的每一列。
-
Textblob是 Python 中的情感分析库。它可以从名为sentiment.polarity的方法中计算情感分数,该方法的得分范围为-1(非常负面)到+1(非常正面),0 为中性。此外,Textblob还可以从 0(非常客观)到 1(非常主观)测量主观性;尽管在本章中我们不会测量主观性。 -
将 Python 函数应用于 Spark 数据框有几个步骤:
-
导入
Textblob并将名为sentiment_score的函数应用于chat列,以生成每个机器人对话的情感极性,并在新列中生成情感分数,也称为sentiment_score。 -
Python 函数不能直接应用于 Spark 数据框,而必须先经过用户定义函数转换
udf,然后在 Spark 中应用。 -
此外,函数的输出也必须明确说明,无论是整数还是浮点数据类型。在我们的情况下,我们明确说明函数的输出将使用
FloatType() from pyspark.sql.types。最后,使用udf情感分数函数内的lambda函数在每行上应用情感。 -
通过执行
df.show(),可以看到具有新创建字段情感分数的更新后的数据框,如下截屏所示:
- 现在,对于聊天对话中的每个响应计算了
sentiment_score之后,我们可以为每行指定-1(非常负面的极性)到+1(非常正面的极性)的值范围。就像我们对计数和平均词数所做的那样,我们可以比较升级对话在情感上是否比不升级对话更积极或更消极。我们可以通过label计算平均情感分数avg_sentiment_score,如下截屏所示:
- 最初,假设
升级对话的极性得分会比不升级更负面是有道理的。实际上,我们发现升级在极性上比不升级稍微更积极;但是,两者都相当中性,因为它们接近 0。
另请参阅
要了解有关TextBlob库的更多信息,请访问以下网站:
textblob.readthedocs.io/en/dev/
从文本中删除停用词
停用词是英语中非常常见的单词,通常会从常见的 NLP 技术中删除,因为它们可能会分散注意力。常见的停用词可能是诸如the或and之类的单词。
准备工作
本节需要导入以下库:
from pyspark.ml.feature import StopWordsRemover
from pyspark.ml import Pipeline
操作步骤...
本节介绍了删除停用词的步骤。
- 执行以下脚本,将
chat中的每个单词提取为数组中的字符串:
df = df.withColumn('words',F.split(F.col('chat'),' '))
- 使用以下脚本将一组常见单词分配给变量
stop_words,这些单词将被视为停用词:
stop_words = ['i','me','my','myself','we','our','ours','ourselves',
'you','your','yours','yourself','yourselves','he','him',
'his','himself','she','her','hers','herself','it','its',
'itself','they','them','their','theirs','themselves',
'what','which','who','whom','this','that','these','those',
'am','is','are','was','were','be','been','being','have',
'has','had','having','do','does','did','doing','a','an',
'the','and','but','if','or','because','as','until','while',
'of','at','by','for','with','about','against','between',
'into','through','during','before','after','above','below',
'to','from','up','down','in','out','on','off','over','under',
'again','further','then','once','here','there','when','where',
'why','how','all','any','both','each','few','more','most',
'other','some','such','no','nor','not','only','own','same',
'so','than','too','very','can','will','just','don','should','now']
- 执行以下脚本,从 PySpark 导入
StopWordsRemover函数,并配置输入和输出列words和word without stop:
from pyspark.ml.feature import StopWordsRemover
stopwordsRemovalFeature = StopWordsRemover(inputCol="words",
outputCol="words without stop").setStopWords(stop_words)
- 执行以下脚本以导入 Pipeline 并为将应用于数据框的停用词转换过程定义
stages:
from pyspark.ml import Pipeline
stopWordRemovalPipeline = Pipeline(stages=[stopwordsRemovalFeature])
pipelineFitRemoveStopWords = stopWordRemovalPipeline.fit(df)
- 最后,使用以下脚本将停用词移除转换
pipelineFitRemoveStopWords应用于数据框df:
df = pipelineFitRemoveStopWords.transform(df)
工作原理...
本节解释了如何从文本中删除停用词。
-
就像我们在对
chat数据进行分析时一样,我们也可以调整chat对话的文本,并将每个单词分解为单独的数组。这将用于隔离停用词并将其删除。 -
将每个单词提取为字符串的新列称为
words,可以在以下截屏中看到:
-
有许多方法可以将一组单词分配给停用词列表。其中一些单词可以使用适当的 Python 库
nltk(自然语言工具包)自动下载和更新。对于我们的目的,我们将利用一个常见的 124 个停用词列表来生成我们自己的列表。可以轻松地手动添加或从列表中删除其他单词。 -
停用词不会为文本增添任何价值,并且将通过指定
outputCol="words without stop"从新创建的列中删除。此外,通过指定inputCol = "words"来设置将用作转换源的列。 -
我们创建一个管道,
stopWordRemovalPipeline,来定义将转换数据的步骤或阶段的顺序。在这种情况下,唯一用于转换数据的阶段是特征stopwordsRemover。 -
管道中的每个阶段都可以具有转换角色和估计角色。估计角色
pipeline.fit(df)用于生成名为pipelineFitRemoveStopWords的转换器函数。最后,在数据框上调用transform(df)函数,以生成具有名为words without stop的新列的更新后的数据框。我们可以将两列并排比较以查看差异,如下截屏所示:
- 新列
words without stop不包含原始列words中被视为停用词的任何字符串。
另请参阅
要了解有关nltk的停用词的更多信息,请访问以下网站:
要了解更多关于 Spark 机器学习管道的信息,请访问以下网站:
spark.apache.org/docs/2.2.0/ml-pipeline.html
要了解 PySpark 中StopWordsRemover功能的更多信息,请访问以下网站:
spark.apache.org/docs/2.2.0/api/python/pyspark.ml.html#pyspark.ml.feature.StopWordsRemover
训练 TF-IDF 模型
我们现在准备训练我们的 TF-IDF NLP 模型,并查看是否可以将这些交易分类为升级或不升级。
准备工作
本节将需要从spark.ml.feature和spark.ml.classification导入。
操作步骤...
以下部分将逐步介绍训练 TF-IDF 模型的步骤。
- 创建一个新的用户定义函数
udf,使用以下脚本为label列定义数值:
label = F.udf(lambda x: 1.0 if x == 'escalate' else 0.0, FloatType())
df = df.withColumn('label', label('label'))
- 执行以下脚本以设置单词向量化的 TF 和 IDF 列:
import pyspark.ml.feature as feat
TF_ = feat.HashingTF(inputCol="words without stop",
outputCol="rawFeatures", numFeatures=100000)
IDF_ = feat.IDF(inputCol="rawFeatures", outputCol="features")
- 使用以下脚本设置管道
pipelineTFIDF,以设置TF_和IDF_的阶段顺序:
pipelineTFIDF = Pipeline(stages=[TF_, IDF_])
- 使用以下脚本将 IDF 估计器拟合到数据框
df上:
pipelineFit = pipelineTFIDF.fit(df)
df = pipelineFit.transform(df)
- 使用以下脚本将数据框拆分为 75:25 的比例,用于模型评估目的:
(trainingDF, testDF) = df.randomSplit([0.75, 0.25], seed = 1234)
- 使用以下脚本导入和配置分类模型
LogisticRegression:
from pyspark.ml.classification import LogisticRegression
logreg = LogisticRegression(regParam=0.25)
- 将逻辑回归模型
logreg拟合到训练数据框trainingDF上。基于逻辑回归模型的transform()方法,创建一个新的数据框predictionDF,如下脚本所示:
logregModel = logreg.fit(trainingDF)
predictionDF = logregModel.transform(testDF)
工作原理...
以下部分解释了如何有效地训练 TF-IDF NLP 模型。
- 最好将标签以数值格式而不是分类形式呈现,因为模型能够在将输出分类为 0 和 1 之间时解释数值。因此,
label列下的所有标签都转换为 0.0 或 1.0 的数值label,如下截图所示:
-
TF-IDF 模型需要通过从
pyspark.ml.feature导入HashingTF和IDF来进行两步处理,以处理不同的任务。第一个任务仅涉及导入HashingTF和IDF并为输入和随后的输出列分配值。numfeatures参数设置为 100,000,以确保它大于数据框中单词的不同数量。如果numfeatures小于不同的单词计数,模型将不准确。 -
如前所述,管道的每个步骤都包含一个转换过程和一个估计器过程。管道
pipelineTFIDF被配置为按顺序排列步骤,其中IDF将跟随HashingTF。 -
HashingTF用于将words without stop转换为新列rawFeatures中的向量。随后,rawFeatures将被IDF消耗,以估算大小并适应数据框以生成名为features的新列,如下截图所示:
-
为了培训目的,我们的数据框将以
75:25的比例保守地拆分,随机种子设置为1234。 -
由于我们的主要目标是将每个对话分类为
升级以进行升级或不升级以进行继续的机器人聊天,因此我们可以使用 PySpark 库中的传统分类算法,如逻辑回归模型。逻辑回归模型配置了正则化参数regParam为 0.025。我们使用该参数略微改进模型,以最小化过度拟合,代价是略微偏差。 -
逻辑回归模型在
trainingDF上进行训练和拟合,然后创建一个新的数据框predictionDF,其中包含新转换的字段prediction,如下截图所示:
还有更多...
虽然我们确实使用了用户定义的函数udf来手动创建一个数值标签列,但我们也可以使用 PySpark 的内置功能StringIndexer来为分类标签分配数值。要查看StringIndexer的操作,请访问第五章,使用 Spark ML 预测消防部门呼叫。
另请参阅
要了解有关 PySpark 中 TF-IDF 模型的更多信息,请访问以下网站:
spark.apache.org/docs/latest/mllib-feature-extraction.html#tf-idf
评估 TF-IDF 模型性能
此时,我们已准备好评估我们模型的性能
准备工作
本节将需要导入以下库:
-
来自
sklearn的metrics -
pyspark.ml.evaluation中的BinaryClassificationEvaluator
如何做...
本节介绍了评估 TF-IDF NLP 模型的步骤。
- 使用以下脚本创建混淆矩阵:
predictionDF.crosstab('label', 'prediction').show()
- 使用以下脚本从
sklearn评估模型的metrics:
from sklearn import metrics
actual = predictionDF.select('label').toPandas()
predicted = predictionDF.select('prediction').toPandas()
print('accuracy score: {}%'.format(round(metrics.accuracy_score(actual, predicted),3)*100))
- 使用以下脚本计算 ROC 分数:
from pyspark.ml.evaluation import BinaryClassificationEvaluator
scores = predictionDF.select('label', 'rawPrediction')
evaluator = BinaryClassificationEvaluator()
print('The ROC score is {}%'.format(round(evaluator.evaluate(scores),3)*100))
它是如何工作的...
本节解释了我们如何使用评估计算来确定模型的准确性。
- 混淆矩阵有助于快速总结实际结果和预测结果之间的准确性数字。由于我们有 75:25 的分割,我们应该从训练数据集中看到 25 个预测。我们可以使用以下脚本构建混淆矩阵:
predictionDF.crosstab('label', 'prediction').show()。脚本的输出可以在以下截图中看到:
- 我们现在处于通过比较
prediction值和实际label值来评估模型准确度的阶段。sklearn.metrics接受两个参数,与label列相关联的actual值,以及从逻辑回归模型派生的predicted值。
请注意,我们再次将 Spark 数据框的列值转换为 pandas 数据框,使用toPandas()方法。
- 创建了两个变量
actual和predicted,并使用metrics.accuracy_score()函数计算了 91.7%的准确度分数,如下截图所示:
- ROC(接收器操作特性)通常与测量真正率相对于假正率的曲线相关联。曲线下面积越大,越好。与曲线相关的 ROC 分数是另一个指标,可用于衡量模型的性能。我们可以使用
BinaryClassificationEvaluator计算ROC,如下截图所示:
另请参阅
要了解有关 PySpark 中的BinaryClassificationEvaluator的更多信息,请访问以下网站:
将模型性能与基线分数进行比较
虽然我们的模型具有 91.7%的高准确度分数,这很好,但将其与基线分数进行比较也很重要。我们在本节中深入探讨了这个概念。
如何做...
本节介绍了计算基线准确度的步骤。
- 执行以下脚本以从
describe()方法中检索平均值:
predictionDF.describe('label').show()
- 减去
1-平均值分数以计算基线准确度。
它是如何工作的...
本节解释了基线准确度背后的概念,以及我们如何使用它来理解模型的有效性。
-
如果每个
chat对话都被标记为do_not_escalate或反之亦然,我们是否会有高于 91.7%的基准准确率?找出这一点最简单的方法是使用以下脚本在predictionDF的label列上运行describe()方法:predictionDF.describe('label').show() -
可以在以下截图中看到脚本的输出:
-
label的平均值为 0.2083 或约 21%,这意味着label为 1 的情况仅发生了 21%的时间。因此,如果我们将每个对话标记为do_not_escalate,我们将有大约 79%的准确率,这低于我们的模型准确率 91.7%。 -
因此,我们可以说我们的模型表现比盲目基准性能模型更好。
另请参阅
要了解 PySpark 数据框中describe()方法的更多信息,请访问以下网站:
spark.apache.org/docs/2.2.0/api/python/pyspark.sql.html#pyspark.sql.DataFrame.describe
第八章:使用 XGBoost 进行房地产价值预测
房地产市场是定价最具竞争力的市场之一。这往往会根据诸多因素而显著变化,如位置、物业年龄、大小等。因此,准确预测房地产价格(特别是房地产市场中的价格)已成为一个现代挑战,以便做出更好的投资决策。本章将处理这个问题。
阅读完本章后,您将能够:
-
下载金县房屋销售数据集
-
进行探索性分析和可视化
-
绘制价格与其他特征之间的相关性
-
预测房屋价格
下载金县房屋销售数据集
在没有数据集的情况下,我们无法构建模型。我们将在本节中下载我们的数据。
准备工作
Kaggle (www.kaggle.com/)是一个用于预测建模和分析竞赛的平台,统计学家和数据挖掘者在这里竞争,以产生最佳的模型来预测和描述由公司和用户上传的数据集。金县房屋销售数据集包含了在 1900 年至 2015 年间在纽约金县出售的 21,613 套房屋的记录。数据集还包含了每套房屋的 21 个不同变量,如位置、邮政编码、卧室数量、生活空间面积等。
如何做...
-
可以从以下网站访问数据集:
www.kaggle.com/harlfoxem/housesalesprediction。数据集来自金县的公共记录,可以免费下载和在任何分析中使用。 -
一旦您到达网站,您可以点击下载按钮,如下图所示:
金县房屋销售数据集
-
从压缩下载的文件
housesalesprediction.zip中出现一个名为kc_house_data.csv的文件。 -
将名为
kc_house_data.csv的文件保存在当前工作目录中,因为这将是我们的数据集。这将被加载到 IPython 笔记本中进行分析和预测。
它是如何工作的...
- 使用以下代码安装本章所需的必要库:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import mpl_toolkits
from sklearn import preprocessing
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.feature_selection import RFE
from sklearn import linear_model
from sklearn.cross_validation import train_test_split %matplotlib inline
- 前面的步骤应该会产生一个输出,如下面的屏幕截图所示:
- 检查当前工作目录并将其设置为存储数据集的目录是一个好主意。如下图所示:
在我们的案例中,名为Chapter 10的文件夹被设置为当前工作目录。
- 使用
read_csv()函数将文件中的数据读入名为dataframe的 Pandas 数据框中,并使用list(dataframe)命令列出特征/标题,如下图所示:
您可能已经注意到,数据集包含 21 个不同的变量,如 id、日期、价格、卧室、浴室等。
还有更多...
本章中使用的库及其函数如下:
-
Numpy,用于整理数组形式的数据以及以数组形式存储名称列表 -
Pandas,用于所有数据整理和数据框形式的数据管理 -
Seaborn,这是一种用于探索性分析和绘图的可视化库 -
MPL_Toolkits,其中包含Matplotlib所需的许多函数和依赖项 -
本章所需的主要科学和统计库
Scikit Learn库中的函数 -
我们还需要一些其他库,如
XGBoost,但这些将在构建模型时根据需要导入。
另请参阅
有关不同库的更多文档可以通过访问以下链接找到:
进行探索性分析和可视化
在预测price等变量时,可视化数据并了解因变量受其他变量影响的方式有助于预测。探索性分析提供了许多数据中不容易获得的见解。本章的这一部分将描述如何从大数据中可视化并得出见解。
准备工作
- 可以使用
dataframe.head()函数打印dataframe的头部,产生如下屏幕截图所示的输出:
- 同样,可以使用
dataframe.tail()函数打印dataframe的尾部,产生如下屏幕截图所示的输出:
dataframe.describe()函数用于获得一些基本统计数据,如每列的最大值、最小值和平均值。如下屏幕截图所示:
dataframe.describe()函数输出
-
正如您所看到的,数据集中有 21,613 条记录,记录了 1900 年至 2015 年间售出的房屋。
-
仔细观察统计数据,我们意识到大多数售出的房屋平均有大约三间卧室。我们还可以看到房屋中卧室数量的最小值为 0,最大的房屋有 33 间卧室,占地面积为 13,540 平方英尺。
如何做到...
- 让我们绘制整个数据集中卧室数量的计数,以了解三居室房屋与两居室或一居室房屋的情况。使用以下代码完成:
dataframe['bedrooms'].value_counts().plot(kind='bar') plt.title('No. of bedrooms')
plt.xlabel('Bedrooms')
plt.ylabel('Count')
sns.despine
- 我们也可以使用以下命令绘制相同数据的饼图:
dataframe['bedrooms'].value_counts().plot(kind='pie')
plt.title('No. of bedrooms')
- 接下来,让我们尝试看看金县最常售出的房屋有多少层。这可以通过使用以下命令绘制条形图来完成:
dataframe['floors'].value_counts().plot(kind='bar') plt.title('Number of floors')
plt.xlabel('No. of floors')
plt.ylabel('Count')
sns.despine
- 接下来,我们需要了解哪些地点售出的房屋数量最多。我们可以使用数据集中的
latitude和longitude变量来做到这一点,如下代码所示:
plt.figure(figsize=(20,20))
sns.jointplot(x=dataframe.lat.values, y=dataframe.long.values, size=9)
plt.xlabel('Longitude', fontsize=10)
plt.ylabel('Latitude', fontsize=10)
plt.show()
sns.despine()
- 让我们也看看不同卧室数量的房屋价格如何相比,执行以下命令:
plt.figure(figsize=(20,20))
sns.jointplot(x=dataframe.lat.values, y=dataframe.long.values, size=9)
plt.xlabel('Longitude', fontsize=10)
plt.ylabel('Latitude', fontsize=10)
plt.show()
sns.despine()
- 使用以下命令获得房屋价格与卧室数量的图:
plt.figure(figsize=(20,20))
sns.jointplot(x=dataframe.lat.values, y=dataframe.long.values, size=9)
plt.xlabel('Longitude', fontsize=10)
plt.ylabel('Latitude', fontsize=10)
plt.show()
sns.despine()
- 同样,让我们看看价格与所有售出房屋的居住面积的比较。这可以通过使用以下命令来完成:
plt.figure(figsize=(8,8))
plt.scatter(dataframe.price, dataframe.sqft_living)
plt.xlabel('Price')
plt.ylabel('Square feet')
plt.show()
- 售出的房屋的条件也给了我们一些重要的信息。让我们将其与价格绘制在一起,以更好地了解一般趋势。使用以下命令完成:
plt.figure(figsize=(5,5))
plt.bar(dataframe.condition, dataframe.price)
plt.xlabel('Condition')
plt.ylabel('Price')
plt.show()
- 我们可以使用以下命令查看哪些邮政编码在金县有最多的房屋销售:
plt.figure(figsize=(8,8))
plt.scatter(dataframe.zipcode, dataframe.price)
plt.xlabel('Zipcode')
plt.ylabel('Price')
plt.show()
- 最后,绘制每个房屋的等级与价格的关系,以了解基于每个房屋的等级的房屋销售趋势,使用以下命令:
plt.figure(figsize=(10,10))
plt.scatter(dataframe.grade, dataframe.price)
plt.xlabel('Grade')
plt.ylabel('Price')
plt.show()
工作原理...
- 卧室数量的图必须给出输出,如下屏幕截图所示:
-
很明显,三居室的房屋销售最多,其次是四居室,然后是两居室,然后是令人惊讶的五居室和六居室。
-
卧室数量的饼图输出如下屏幕截图所示:
-
您会注意到,三居室房屋大约占金县所有售出房屋的 50%。大约 25%是四居室房屋,其余 25%由两居室、五居室、六居室等房屋组成。
-
运行脚本以查看按楼层分类的最常售出的房屋时,我们注意到以下输出:
-
很明显,单层房屋销售量最大,其次是两层房屋。超过两层的房屋数量相当少,这可能是家庭规模和居住在金县的居民收入的指示。
-
检查不同位置出售房屋的密度后,我们得到了一个输出,如下面的屏幕截图所示。很明显,一些地方的房屋销售密度比其他地方要高:
-
从前述图表中观察到的趋势,很容易注意到在纬度-122.2 和-122.4 之间销售的房屋数量更多。同样,在经度 47.5 和 47.8 之间销售的房屋密度比其他经度更高。这可能是其他社区相比,更安全、更宜居社区的指示。
-
在绘制房屋价格与房屋卧室数量的关系时,我们意识到房屋卧室数量与价格之间的趋势与价格成正比,直到六个卧室,然后变为反比,如下面的屏幕截图所示:
- 将每个房屋的居住面积与价格进行对比,我们发现价格随着房屋面积的增加而增加的趋势。最昂贵的房屋似乎有 12000 平方英尺的居住面积,如下面的屏幕截图所示:
还有更多...
- 在绘制房屋状况与价格的关系时,我们再次注意到了一个预期的趋势,即随着房屋状况评分的提高,价格也在增加,如下面的屏幕截图所示。有趣的是,五卧室房屋的平均价格比四卧室房屋要低,这可能是因为对这么大的房子的购买者较少:
- 房屋邮政编码与价格的图表显示了不同邮政编码地区房屋价格的趋势。您可能已经注意到,某些邮政编码,如 98100 至 98125 之间的邮政编码,比其他地区有更多的房屋销售密度,而 98040 邮政编码地区的房屋价格高于平均价格,可能表明这是一个更富裕的社区,如下面的屏幕截图所示:
- 房屋等级与价格的图表显示,随着等级的提高,价格呈一致增长的趋势。两者之间似乎存在明显的线性关系,如下面的屏幕截图所示:
另请参阅
在对数据进行任何模型运行之前,以下链接很好地解释了为什么数据可视化如此重要:
-
www.slideshare.net/Centerline_Digital/the-importance-of-data-visualization -
www.techchange.org/2015/05/19/data-visualization-analysis-international-development/
绘制价格与其他特征之间的相关性
现在初步的探索性分析已经完成,我们对不同变量如何影响每个房屋的价格有了更好的了解。然而,我们不知道每个变量在预测价格时的重要性。由于我们有 21 个变量,如果将所有变量合并到一个模型中,建模就会变得困难。因此,一些变量可能需要被丢弃或忽略,如果它们的重要性不如其他变量。
准备工作
相关系数在统计学中用于衡量两个变量之间的关系强度。特别是,在进行线性回归时,皮尔逊相关系数是最常用的系数。相关系数通常取-1 到+1 之间的值:
-
相关系数为 1 意味着对于一个变量的正增加,另一个变量也会以固定比例正增加。例如,鞋子尺寸几乎与脚长完美相关。
-
相关系数为-1 意味着对于一个变量的正增加,另一个变量会以固定比例负减少。例如,油箱中的汽油量几乎与加速度或齿轮机构完美相关(在一档行驶的时间较长时,与四档相比,汽油的使用量减少)。
-
零意味着对于每次增加,没有正面或负面的增加。两者之间没有关系。
如何做...
- 通过使用以下命令从数据集中删除
id和date特征开始。在我们的预测中,我们不会使用它们,因为 ID 变量都是唯一的,在我们的分析中没有价值,而日期需要使用不同的函数来正确处理它们。这留给读者自己练习:
x_df = dataframe.drop(['id','date',], axis = 1)
x_df
- 使用以下命令将因变量(在本例中为房价)复制到新的
dataframe中:
y = dataframe[['price']].copy()
y_df = pd.DataFrame(y)
y_df
- 价格和其他每个变量之间的相关性可以通过以下脚本手动找到:
print('Price Vs Bedrooms: %s' % x_df['price'].corr(x_df['bedrooms']))
print('Price Vs Bathrooms: %s' % x_df['price'].corr(x_df['bathrooms']))
print('Price Vs Living Area: %s' % x_df['price'].corr(x_df['sqft_living']))
print('Price Vs Plot Area: %s' % x_df['price'].corr(x_df['sqft_lot']))
print('Price Vs No. of floors: %s' % x_df['price'].corr(x_df['floors']))
print('Price Vs Waterfront property: %s' % x_df['price'].corr(x_df['waterfront']))
print('Price Vs View: %s' % x_df['price'].corr(x_df['view']))
print('Price Vs Grade: %s' % x_df['price'].corr(x_df['grade']))
print('Price Vs Condition: %s' % x_df['price'].corr(x_df['condition']))
print('Price Vs Sqft Above: %s' % x_df['price'].corr(x_df['sqft_above']))
print('Price Vs Basement Area: %s' % x_df['price'].corr(x_df['sqft_basement']))
print('Price Vs Year Built: %s' % x_df['price'].corr(x_df['yr_built']))
print('Price Vs Year Renovated: %s' % x_df['price'].corr(x_df['yr_renovated']))
print('Price Vs Zipcode: %s' % x_df['price'].corr(x_df['zipcode']))
print('Price Vs Latitude: %s' % x_df['price'].corr(x_df['lat']))
print('Price Vs Longitude: %s' % x_df['price'].corr(x_df['long']))
- 除了前面的方法,还可以通过以下方式使用一个命令在一个
dataframe中找到一个变量与所有其他变量(或列)之间的相关性:
x_df.corr().iloc[:,-19]
- 相关变量可以使用
seaborn库和以下脚本绘制:
sns.pairplot(data=x_df,
x_vars=['price'],
y_vars=['bedrooms', 'bathrooms', 'sqft_living',
'sqft_lot', 'floors', 'waterfront','view',
'grade','condition','sqft_above','sqft_basement',
'yr_built','yr_renovated','zipcode','lat','long'],
size = 5)
它是如何工作的...
- 删除
id和date变量后,新的名为x_df的dataframe包含 19 个变量或列,如以下截图所示。对于本书的目的,只打印出前十个条目:
输出的前 10 个条目
- 创建一个只包含因变量(价格)的新
dataframe,您将看到以下输出。这个新的dataframe名为y_df。同样,为了说明,只打印价格列的前十个条目:
- 价格和其他变量之间的相关性显示在以下截图中:
- 您可能已经注意到,
sqft_living变量与价格的相关性最高,相关系数为 0.702035。其次是grade,相关系数为 0.667434,其次是sqft_above,相关系数为 0.605567。Zipcode与价格的相关性最低,相关系数为-0.053202。
还有更多...
- 使用简化代码找到的相关系数给出了完全相同的值,但也给出了价格与自身的相关性,结果是 1.0000,这是预期的。如以下截图所示:
- 使用
seaborn库绘制的相关系数在以下截图中呈现。请注意,每个图中价格都在 x 轴上:
相关系数的绘制
另请参阅
以下链接提供了对皮尔逊相关系数的出色解释以及如何手动计算它:
en.wikipedia.org/wiki/Pearson_correlation_coefficient
www.statisticshowto.com/probability-and-statistics/correlation-coefficient-formula/
预测房价
本节将使用当前数据框中的所有特征构建一个简单的线性模型来预测房价。然后,我们将评估模型,并尝试在本节的后半部分使用更复杂的模型来提高准确性。
准备工作
访问以下链接以了解线性回归的工作原理以及如何在 Scikit Learn 库中使用线性回归模型:
en.wikipedia.org/wiki/Linear_regression
www.stat.yale.edu/Courses/1997-98/101/linreg.htm
newonlinecourses.science.psu.edu/stat501/node/251/
scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html
scikit-learn.org/stable/modules/linear_model.html
如何做...
- 使用以下脚本从
x_df数据框中删除Price列,并将其保存到名为x_df2的新数据框中:
x_df2 = x_df.drop(['price'], axis = 1)
- 声明一个名为
reg的变量,并使用以下脚本将其等于 Scikit Learn 库中的LinearRegression()函数:
reg=linear_model.LinearRegression()
- 使用以下脚本将数据集分割为测试集和训练集:
x_train,x_test,y_train,y_test = train_test_split(x_df2,y_df,test_size=0.4,random_state=4)
- 使用以下脚本在训练集上拟合模型:
reg.fit(x_train,y_train)
-
通过使用
reg.coef_命令打印应用线性回归到训练集和测试集生成的系数。 -
使用以下脚本生成的模型预测列进行查看:
predictions=reg.predict(x_test)
predictions
- 使用以下命令打印模型的准确性:
reg.score(x_test,y_test)
它是如何工作的...
- 将回归模型拟合到训练集后的输出必须如下截屏所示:
reg.coeff_命令生成了 18 个系数,每个系数对应数据集中的一个变量,如下截屏所示:
-
具有最正值的特征/变量的系数在价格预测中具有更高的重要性,与具有负值的特征/变量的系数相比。这是回归系数的主要重要性。
-
打印预测时,您必须看到一个输出,其中包含从 1 到 21,612 的值数组,数据集中的每一行都有一个值,如下截屏所示:
- 最后,打印模型的准确性,我们获得了 70.37%的准确性,对于线性模型来说并不差。如下截屏所示:
还有更多...
线性模型在第一次尝试时表现不错,但如果我们希望模型更准确,我们将不得不使用一个带有一些非线性的更复杂模型,以便很好地拟合所有数据点。XGBoost 是我们将在本节中使用的模型,以尝试通过线性回归提高准确性。这是以下方式完成的:
-
使用
import xgboost命令导入XGBoost库。 -
如果出现错误,您将不得不通过终端进行库的 pip 安装。这可以通过打开一个新的终端窗口并发出以下命令来完成:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- 在这个阶段,您必须看到一个输出,其外观如下截屏所示:
- 在这个阶段,您将被提示输入密码。安装 Homebrew 后,您将看到如下截屏所示的输出:
- 接下来,使用以下命令安装 Python:
brew install python
-
使用
brew doctor命令检查您的安装并遵循 homebrew 的建议。 -
一旦安装了
Homebrew,请使用以下命令 pip 安装 XGBoost:
pip install xgboost
- 安装完成后,您应该能够将 XGBoost 导入到 IPython 环境中。
一旦 XGBoost 成功导入到 Jupyter 环境中,您就可以使用库中的函数声明和存储模型。可以按以下步骤完成:
- 声明一个名为
new_model的变量来存储模型,并使用以下命令声明所有超参数:
new_model = xgboost.XGBRegressor(n_estimators=750, learning_rate=0.09, gamma=0, subsample=0.65, colsample_bytree=1, max_depth=7)
- 上述命令的输出必须看起来像以下截图中显示的那样:
- 将数据分割为测试集和训练集,并使用以下命令将新模型拟合到拆分数据中:
from sklearn.model_selection import train_test_split
traindf, testdf = train_test_split(x_train, test_size = 0.2)
new_model.fit(x_train,y_train)
- 在这一点上,您将看到类似以下截图中显示的输出:
- 最后,使用新拟合的模型预测房屋价格,并使用以下命令评估新模型:
from sklearn.metrics import explained_variance_score
predictions = new_model.predict(x_test)
print(explained_variance_score(predictions,y_test))
- 执行上述命令时,您必须看到类似以下截图中显示的输出:
-
请注意,新模型的准确性现在为 87.79%,约为 88%。这被认为是最佳的。
-
在这种情况下,
估计器数量设置为 750。在 100 到 1,000 之间进行实验后,确定 750 个估计器给出了最佳准确性。学习率设置为 0.09。子采样率设置为 65%。最大深度设置为 7。max_depth对模型准确性似乎没有太大影响。然而,使用较慢的学习率确实提高了准确性。通过尝试各种超参数,我们能够进一步将准确性提高到 89%。 -
未来的步骤涉及对诸如卧室、浴室、楼层、邮政编码等变量进行独热编码,并在模型拟合之前对所有变量进行归一化。尝试调整超参数,如学习率、XGBoost 模型中的估计器数量、子采样率等,以查看它们如何影响模型准确性。这留给读者作为练习。
-
此外,您可能希望尝试结合 XGBoost 和交叉验证,以找出模型中树的最佳数量,从而进一步提高准确性。
-
可以进行的另一个练习是使用不同大小的测试和训练数据集,以及在训练过程中合并
date变量。在我们的情况下,我们将其分割为 80%的训练数据和 20%的测试数据。尝试将测试集增加到 40%,看看模型准确性如何变化。
另请参阅
访问以下链接以了解如何调整 XGBoost 模型中的超参数以及如何在 XGBoost 中实现交叉验证:
xgboost.readthedocs.io/en/latest/python/index.html
xgboost.readthedocs.io/en/latest/get_started/
第九章:使用 LSTM 预测苹果股票市场成本
多年来一直有股票市场预测,并且已经产生了整个预言家行业。这并不奇怪,因为如果预测正确,它可以带来可观的利润。了解何时是买入或卖出股票的好时机是在华尔街占据上风的关键。本章将专注于使用 Keras 上的 LSTM 创建深度学习模型来预测 AAPL 的股票市场报价。
本章将涵盖以下配方:
-
下载苹果的股票市场数据
-
探索和可视化苹果的股票市场数据
-
为模型性能准备股票数据
-
构建 LSTM 模型
-
评估 LSTM 模型
下载苹果的股票市场数据
有许多资源可用于下载苹果的股票市场数据。对于我们的目的,我们将使用 Yahoo! Finance 网站。
准备工作
本节将需要初始化一个 Spark 集群,该集群将用于本章中的所有配方。可以在终端使用sparknotebook初始化 Spark 笔记本,如下屏幕截图所示:
可以使用以下脚本在 Jupyter 笔记本中初始化SparkSession:
spark = SparkSession.builder \
.master("local") \
.appName("StockMarket") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
如何做到...
以下部分将介绍下载苹果的历史股票市场数据的步骤。
-
访问以下网站,跟踪苹果的每日历史调整收盘股票价值,其股票代码为 AAPL:
finance.yahoo.com/quote/AAPL/history -
设置并应用以下参数到历史数据选项卡:
-
时间段:2000 年 1 月 1 日至 2018 年 4 月 30 日。
-
显示:历史价格。
-
频率:每日。
-
通过单击下载数据链接,使用指定参数将数据集下载到
.csv文件中,如下屏幕截图所示:
- 下载文件
AAPL.csv,然后使用以下脚本将相同的数据集上传到 Spark 数据框中:
df =spark.read.format('com.databricks.spark.csv')\
.options(header='true', inferschema='true')\
.load('AAPL.csv')
工作原理...
以下部分解释了如何将股票市场数据纳入 Jupyter 笔记本。
-
Yahoo! Finance 是公开交易公司股票市场报价的重要来源。苹果的股票报价 AAPL 在纳斯达克交易,可以捕获历史报价以进行模型开发和分析。Yahoo! Finance 提供了在每日、每周或每月快照上捕获股票报价的选项。
-
本章的目的是在每日级别预测股票,因为这将为我们的训练模型带来最多的数据。我们可以通过追溯数据到 2000 年 1 月 1 日,一直到 2018 年 4 月 30 日来实现这一点。
-
一旦我们设置了下载参数,我们就会从 Yahoo! Finance 收到一个格式良好的逗号分隔值文件,可以很容易地转换为具有最少问题的 Spark 数据框。
-
数据框将允许我们每天查看股票的日期、开盘价、最高价、最低价、收盘价、调整收盘价和成交量。数据框中的列跟踪开盘和收盘股票价值,以及当天交易的最高和最低价值。还捕获了当天交易的股票数量。Spark 数据框的输出
df可以通过执行df.show()来显示,如下面的屏幕截图所示:
还有更多...
Python 有股票市场 API,允许您自动连接并拉取公开交易公司(如苹果)的股票市场报价。您需要输入参数并检索可以存储在数据框中的数据。然而,截至 2018 年 4 月,Yahoo! Finance API 不再运作,因此不是提取本章数据的可靠解决方案。
另请参阅
Pandas_datareader 是一个非常强大的库,可以从网站上提取数据,例如 Yahoo! Finance。要了解更多关于该库以及它如何在恢复在线后与 Yahoo! Finance 连接的信息,请访问以下网站:
github.com/pydata/pandas-datareader
探索和可视化苹果股票市场数据
在对数据进行任何建模和预测之前,首先探索和可视化手头的数据是很重要的,以发现任何隐藏的宝藏。
准备工作
在本节中,我们将对数据框进行转换和可视化。这将需要在 Python 中导入以下库:
-
pyspark.sql.functions -
matplotlib
如何做...
以下部分将介绍探索和可视化股票市场数据的步骤。
- 使用以下脚本通过删除时间戳来转换数据框中的
Date列:
import pyspark.sql.functions as f
df = df.withColumn('date', f.to_date('Date'))
- 创建一个循环来向数据框添加三个额外的列。循环将把
date字段分解为year、month和day,如下面的脚本所示:
date_breakdown = ['year', 'month', 'day']
for i in enumerate(date_breakdown):
index = i[0]
name = i[1]
df = df.withColumn(name, f.split('date', '-')[index])
-
使用以下脚本将 Spark 数据框的子集保存到名为
df_plot的pandas数据框中:df_plot = df.select('year', 'Adj Close').toPandas(). -
使用以下脚本在笔记本中绘制和可视化
pandas数据框df_plot:
from matplotlib import pyplot as plt
%matplotlib inline
df_plot.set_index('year', inplace=True)
df_plot.plot(figsize=(16, 6), grid=True)
plt.title('Apple stock')
plt.ylabel('Stock Quote ($)')
plt.show()
-
使用以下脚本计算我们的 Spark 数据框的行和列数:
df.toPandas().shape。 -
执行以下脚本来确定数据框中的空值:
df.dropna().count()。 -
执行以下脚本来获取
Open、High、Low、Close和Adj Close的统计数据:
df.select('Open', 'High', 'Low', 'Close', 'Adj Close').describe().show()
它是如何工作的...
以下部分解释了探索性数据分析所使用的技术和获得的见解。
- 数据框中的日期列更像是一个带有时间值的日期时间列,所有时间值都以 00:00:00 结尾。这对于我们建模过程中的需求是不必要的,因此可以从数据集中删除。幸运的是,PySpark 有一个
to_date函数可以很容易地做到这一点。数据框df使用withColumn()函数进行转换,现在只显示日期列而没有时间戳,如下面的屏幕截图所示:
- 为了分析目的,我们想要从日期列中提取
day、month和year。我们可以通过枚举一个自定义列表date_breakdown来实现这一点,通过-分割日期,然后使用withColumn()函数为年、月和日添加新列。更新后的数据框中可以看到新添加的列,如下面的屏幕截图所示:
一个重要的要点是,PySpark 也有一个用于日期的 SQL 函数,可以从日期时间戳中提取日、月或年。例如,如果我们要向数据框添加一个月份列,我们将使用以下脚本:df.withColumn("month",f.month("date")).show()。这是为了突出在 Spark 中有多种方法可以转换数据。
-
Spark 数据框的可视化功能比
pandas数据框更有限。因此,我们将从 Spark 数据框df中提取两列,并将它们转换为pandas数据框,以绘制线形或时间序列图。y 轴将是股票的调整收盘价,x 轴将是日期的年份。 -
准备好的 pandas 数据框 df_plot 可以在设置一些格式特性后使用 matplotlib 进行绘制,例如网格可见性、绘图的图形大小以及标题和轴的标签。此外,我们明确指出数据框的索引需要指向年份列。否则,默认索引将出现在 x 轴上而不是年份。最终的时间序列图可以在下面的屏幕截图中看到:
-
在过去的 18 年中,苹果经历了广泛的增长。虽然有几年出现了一些下跌,但总体趋势是稳步上升,过去几年的股票报价在 150 美元和 175 美元之间徘徊。
-
到目前为止,我们对数据框进行了一些更改,因此重要的是要对行和列的总数进行清点,因为这将影响后面在本章中对数据集进行测试和训练的方式。如下截图所示,我们总共有 10 列和 4,610 行:
-
当执行
df.dropna().count()时,我们可以看到行数仍然是 4,610,与上一步的行数相同,表明没有任何行具有空值。 -
最后,我们可以得到每个将用于模型的列的行数、均值、标准差、最小值和最大值的良好读数。这可以帮助确定数据中是否存在异常。需要注意的一点是,将用于模型的五个字段的标准差都高于均值,表明数据更分散,而不是围绕均值聚集。可以在以下截图中看到 Open、High、Low、Close 和 Adj Close 的统计数据:
还有更多...
虽然 Spark 中的数据框没有pandas数据框中的本地可视化功能,但有些公司可以通过笔记本提供高级可视化功能,而无需使用诸如matplotlib之类的库。Databricks 是一家提供此功能的公司之一。
以下是使用 Databricks 笔记本中内置功能的可视化示例:
另请参阅
要了解有关 Databricks 的更多信息,请访问以下网站:databricks.com/。
要了解 Databricks 笔记本中的可视化更多信息,请访问以下网站:docs.databricks.com/user-guide/visualizations/index.html。
要了解如何通过 Microsoft Azure 订阅访问 Databricks 的更多信息,请访问以下网站:
azure.microsoft.com/en-us/services/databricks/
为模型性能准备股票数据
我们几乎准备好为苹果的股票价值表现构建预测算法了。手头剩下的任务是以确保最佳预测结果的方式准备数据。
准备工作
在本节中,我们将对数据框执行转换和可视化。这将需要在 Python 中导入以下库:
-
numpy -
MinMaxScaler()
如何做...
本节将介绍为我们的模型准备股票市场数据的步骤。
- 执行以下脚本将年份列按
Adj Close计数分组:
df.groupBy(['year']).agg({'Adj Close':'count'})\
.withColumnRenamed('count(Adj Close)', 'Row Count')\
.orderBy(["year"],ascending=False)\
.show()
- 执行以下脚本创建两个新的用于训练和测试的数据框:
trainDF = df[df.year < 2017]
testDF = df[df.year > 2016]
- 使用以下脚本将两个新数据框转换为
pandas数据框,以获取行和列计数:
trainDF.toPandas().shape
testDF.toPandas().shape
- 与
df之前所做的一样,我们使用以下脚本可视化trainDF和testDF:
trainDF_plot = trainDF.select('year', 'Adj Close').toPandas()
trainDF_plot.set_index('year', inplace=True)
trainDF_plot.plot(figsize=(16, 6), grid=True)
plt.title('Apple Stock 2000-2016')
plt.ylabel('Stock Quote ($)')
plt.show()
testDF_plot = testDF.select('year', 'Adj Close').toPandas()
testDF_plot.set_index('year', inplace=True)
testDF_plot.plot(figsize=(16, 6), grid=True)
plt.title('Apple Stock 2017-2018')
plt.ylabel('Stock Quote ($)')
plt.show()
- 我们根据以下脚本创建两个新数组
trainArray和testArray,除了日期列以外的数据框的数据:
import numpy as np
trainArray = np.array(trainDF.select('Open', 'High', 'Low', 'Close','Volume', 'Adj Close' ).collect())
testArray = np.array(testDF.select('Open', 'High', 'Low', 'Close','Volume', 'Adj Close' ).collect())
- 为了将数组缩放到 0 到 1 之间,从
sklearn导入MinMaxScaler并创建一个函数调用MinMaxScale,使用以下脚本:
from sklearn.preprocessing import MinMaxScaler
minMaxScale = MinMaxScaler()
- 然后在
trainArray上拟合MinMaxScaler并使用以下脚本创建两个新数组,以便进行缩放:
minMaxScale.fit(trainArray)
testingArray = minMaxScale.transform(testArray)
trainingArray = minMaxScale.transform(trainArray)
- 使用以下脚本将
testingArray和trainingArray拆分为特征x和标签y:
xtrain = trainingArray[:, 0:-1]
xtest = testingArray[:, 0:-1]
ytrain = trainingArray[:, -1:]
ytest = testingArray[:, -1:]
- 执行以下脚本以检索所有四个数组的最终形状清单:
print('xtrain shape = {}'.format(xtrain.shape))
print('xtest shape = {}'.format(xtest.shape))
print('ytrain shape = {}'.format(ytrain.shape))
print('ytest shape = {}'.format(ytest.shape))
- 执行以下脚本来绘制报价
open、high、low和close的训练数组:
plt.figure(figsize=(16,6))
plt.plot(xtrain[:,0],color='red', label='open')
plt.plot(xtrain[:,1],color='blue', label='high')
plt.plot(xtrain[:,2],color='green', label='low')
plt.plot(xtrain[:,3],color='purple', label='close')
plt.legend(loc = 'upper left')
plt.title('Open, High, Low, and Close by Day')
plt.xlabel('Days')
plt.ylabel('Scaled Quotes')
plt.show()
- 此外,我们使用以下脚本绘制
volume的训练数组:
plt.figure(figsize=(16,6))
plt.plot(xtrain[:,4],color='black', label='volume')
plt.legend(loc = 'upper right')
plt.title('Volume by Day')
plt.xlabel('Days')
plt.ylabel('Scaled Volume')
plt.show()
工作原理...
本节将解释数据在模型中使用时所需的转换。
- 建立模型的第一步之一是将数据分割为训练和测试数据集,以进行模型评估。我们的目标是使用 2000 年至 2016 年的所有股票报价来预测 2017 年至 2018 年的股票趋势。我们知道从前面的部分我们有总共 4,610 天的股票报价,但我们不知道每年有多少。我们可以使用数据框中的
groupBy()函数来获取每年股票报价的唯一计数,如下图所示:
-
2016 年和 2017 年的合并数据大约占总数据的 7%,这对于测试数据集来说有点小。但是,对于这个模型的目的来说,应该是足够的。剩下的 93%的数据将用于 2000 年至 2016 年的训练。因此,使用筛选器创建了两个数据框,以确定是否包括或排除 2016 年之前或之后的行。
-
我们现在可以看到测试数据集
testDF包含 333 行,而训练数据集trainDF包含 4,277 行。当两者合并时,我们可以得到原始数据框df的总行数为 4,610。最后,我们看到testDF仅由 2017 年和 2018 年的数据组成,2017 年有 251 行,2018 年有 82 行,总共 333 行,如下图所示:
请注意,每当我们将 Spark 数据框转换为pandas数据框时,它可能不适用于大数据。虽然对于我们的特定示例它可以工作,因为我们使用的是相对较小的数据集,但是将数据转换为pandas数据框意味着所有数据都加载到驱动程序的内存中。一旦发生这种转换,数据就不会存储在 Spark 工作节点中,而是存储在主驱动节点中。这并不是最佳的做法,可能会产生内存不足的错误。如果您发现需要将 Spark 转换为pandas数据框来可视化数据,建议从 Spark 中提取一个随机样本,或者将 Spark 数据聚合到一个更易管理的数据集中,然后在pandas中进行可视化。
- 一旦将数据的子集转换为
toPandas()以利用pandas的内置绘图功能,就可以使用matplotlib可视化测试和训练数据框。将数据框并排可视化展示了当未缩放调整收盘价的 y 轴时,图表看起来相似。实际上,我们可以看到trainDF_plot从 0 开始,而testDF_plot从 110 开始,如下两个截图所示。
- 目前我们的股票价值不适合深度学习建模,因为没有归一化或标准化的基线。在使用神经网络时,最好将值保持在 0 到 1 之间,以匹配 Sigmoid 或 Step 函数中的结果,这些函数用于激活。为了实现这一点,我们必须首先将
pyspark数据框trainDF和testDF转换为numpy数组,即trainArray和testArray。由于这些现在是数组而不是数据框,我们将不再使用日期列,因为神经网络只对数值感兴趣。每个数组的第一个值可以在以下截图中看到:
-
有许多方法可以将数组值缩放到 0 到 1 之间的范围。它涉及使用以下公式:
缩放后的数组值 = (数组值 - 最小数组值) / (最大数组值 - 最小数组值)。幸运的是,我们不需要手动计算数组的值。我们可以利用sklearn中的MinMaxScaler()函数来缩放这两个数组。 -
MinMaxScaler()函数适用于训练数组trainArray,然后应用于创建两个全新的数组trainingArray和testingArray,它们的值在 0 到 1 之间进行了缩放。每个数组的第一行可以在下面的截图中看到:
- 现在,我们准备通过将数组切片为测试和训练目的的 x 和 y 来设置我们的标签和特征变量。数组中的前五个元素是特征或 x 值,最后一个元素是标签或 y 值。特征由 Open、High、Low、Close 和 Volume 的值组成。标签由 Adj Close 组成。
trainingArray的第一行的拆分可以在下面的截图中看到:
- 最后,我们将查看我们在模型中将要使用的四个数组的形状,以确认我们有 4,227 个训练数据矩阵行,333 个测试数据矩阵行,5 个特征元素(
x)和 1 个标签元素(y),如下截图所示:
- 训练数组
xtrain的 open、low、high 和 close 的值可以使用新调整的 0 到 1 之间的标度绘制报价,如下截图所示:
- 此外,
volume也可以使用 0 到 1 之间的缩放体积得分绘制,如下截图所示:
还有更多...
虽然我们使用了来自sklearn的MinMaxScaler,但也很重要的是要了解,pyspark.ml.feature中也有一个MinMaxScaler函数可供使用。它的工作方式与sklearn完全相同,通过将每个特征重新缩放为 0 到 1 之间的值。如果我们在本章中使用了 PySpark 中的机器学习库来进行预测,我们将使用pyspark.ml.feature中的MinMaxScaler。
另请参阅
要了解来自sklearn的MinMaxScaler的更多信息,请访问以下网站:
scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html.
要了解来自pyspark的MinMaxScaler的更多信息,请访问以下网站:
spark.apache.org/docs/2.2.0/ml-features.html#minmaxscaler.
构建 LSTM 模型
现在数据以符合 Keras 用于 LSTM 建模的模型开发格式。因此,我们将在本节中设置和配置深度学习模型,以预测 2017 年和 2018 年苹果股票报价。
准备工作
在本节中,我们将对模型进行模型管理和超参数调整。这将需要在 Python 中导入以下库:
from keras import models
from keras import layers
如何做...
本节将介绍设置和调整 LSTM 模型的步骤。
- 使用以下脚本从
keras导入以下库:
from keras import models, layers
- 使用以下脚本构建一个
Sequential模型:
model = models.Sequential()
model.add(layers.LSTM(1, input_shape=(1,5)))
model.add(layers.Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
- 使用以下脚本将测试和训练数据集转换为三维数组:
xtrain = xtrain.reshape((xtrain.shape[0], 1, xtrain.shape[1]))
xtest = xtest.reshape((xtest.shape[0], 1, xtest.shape[1]))
- 使用以下脚本使用名为
loss的变量来fit模型:
loss = model.fit(xtrain, ytrain, batch_size=10, epochs=100)
- 使用以下脚本创建一个新数组
predicted:
predicted = model.predict(xtest)
- 使用以下脚本将
predicted和ytest数组合并成一个统一的数组combined_array:
combined_array = np.concatenate((ytest, predicted), axis = 1)
它是如何工作的...
本节解释了如何配置 LSTM 神经网络模型以在我们的数据集上进行训练。
-
大部分用于构建 LSTM 模型的
keras功能将来自models和layers。 -
构建的
LSTM模型将使用Sequential类进行定义,该类与依赖于序列的时间序列非常匹配。LSTM 模型的input_shape = (1,5),表示我们的训练数据集中有一个因变量和五个自变量。只使用一个Dense层来定义神经网络,因为我们希望保持模型简单。在 keras 中编译模型时需要一个损失函数,由于我们正在对递归神经网络进行操作,因此最好使用mean_squared_error计算来确定预测值与实际值的接近程度。最后,在编译模型时还需要定义一个优化器来调整神经网络中的权重。adam在递归神经网络中表现良好,尤其是在使用时。 -
我们当前的数组
xtrain和xtest目前是二维数组;然而,为了将它们纳入 LSTM 模型中,它们需要使用reshape()转换为三维数组,如下面的屏幕截图所示:
- LSTM 模型使用
xtrain和ytrain进行拟合,批量大小设置为 10,时期数设置为 100。批量大小是定义一起训练的对象数量的设置。我们可以根据需要设置批量大小的大小,但要记住,批量数量越低,需要的内存就越多。此外,时期是模型遍历整个数据集的次数的度量。最终,这些参数可以根据时间和内存分配进行调整。
每个时期的均方误差损失都被捕获并可视化。在第五或第六个时期之后,我们可以看到损失逐渐减小,如下面的屏幕截图所示:
- 我们现在可以创建一个新数组
predicted,基于应用于xtest的拟合模型,然后将其与ytest结合在一起,以便进行准确性比较。
另请参阅
要了解更多关于 keras 中参数调整模型的信息,请访问以下网站:keras.io/models/model/
评估模型
现在到了关键时刻:我们将看看我们的模型是否能够为 2017 年和 2018 年的 AAPL 股票提供良好的预测。
准备工作
我们将使用均方误差进行模型评估。因此,我们需要导入以下库:
import sklearn.metrics as metrics
如何做...
本节介绍了可视化和计算 2017 年和 2018 年苹果公司预测与实际股票报价的过程。
- 绘制
Actual与Predicted股票的并排比较图,使用以下脚本:
plt.figure(figsize=(16,6))
plt.plot(combined_array[:,0],color='red', label='actual')
plt.plot(combined_array[:,1],color='blue', label='predicted')
plt.legend(loc = 'lower right')
plt.title('2017 Actual vs. Predicted APPL Stock')
plt.xlabel('Days')
plt.ylabel('Scaled Quotes')
plt.show()
- 使用以下脚本计算实际
ytest与predicted股票之间的均方误差:
import sklearn.metrics as metrics
np.sqrt(metrics.mean_squared_error(ytest,predicted))
工作原理...
本节解释了 LSTM 模型评估的结果。
- 从图形上看,我们可以看到我们的预测与 2017 年至 2018 年的实际股票报价非常接近,如下面的屏幕截图所示:
- 我们的模型显示,对于 2017 年和 2018 年的前几天,预测值与实际值更接近。总的来说,虽然我们的预测值和实际得分似乎非常接近,但最好还是进行均方误差计算,以了解两者之间的偏差有多大。正如我们所看到的,我们的均方误差为 0.05841,约为 5.8%。
另请参阅
要了解更多关于 sklearn 中如何计算均方误差的信息,请访问以下网站:
scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html。