Java-机器学习-三-

51 阅读1小时+

Java 机器学习(三)

八、使用 DL4J 的图像识别

图像在网络服务、社交网络和网络商店中无处不在。与人类相反,计算机很难理解图像中的内容及其代表的意义。在这一章中,我们将首先看看教计算机如何理解图像背后的挑战,然后重点关注一种基于深度学习的方法。我们将查看配置深度学习模型所需的高级理论,并讨论如何使用 Java 库 Deeplearning4j 实现能够对图像进行分类的模型。

本章将涵盖以下主题:

  • 图像识别简介
  • 讨论深度学习基础知识
  • 建立图像识别模型

图像识别简介

图像识别的典型目标是检测和识别数字图像中的对象。图像识别在工厂自动化中应用于监控产品质量;识别潜在危险活动的监视系统,如移动人员或车辆;通过指纹、虹膜或面部特征提供生物识别的安全应用;自动驾驶汽车重建道路和环境条件;诸如此类。

数字图像不是以基于属性的描述的结构化方式呈现的;相反,它们被编码为不同通道中的颜色量,例如,黑白和红绿蓝通道。学习目标是识别与特定对象相关联的模式。传统的图像识别方法包括将图像转换成不同的形式,例如,识别物体的角、边缘、同色斑点和基本形状。这样的模式然后被用来训练学习者区分物体。下面列出了一些传统算法的著名例子:

  • 边缘检测找到图像中对象的边界
  • 角点检测识别两条边缘的交点或其他感兴趣的点,例如线的末端、曲率最大值或最小值等等
  • 斑点检测识别与其周围区域相比在诸如亮度或颜色的属性上不同的区域
  • 脊线检测使用平滑函数识别图像中的其他感兴趣点
  • 尺度不变特征变换 ( SIFT )是一种鲁棒的算法,可以匹配对象,即使它们的尺度或方向不同于数据库中的代表性样本
  • 霍夫变换识别图像中的特定模式

最近的一种方法基于深度学习。深度学习是神经网络的一种形式,它模仿大脑处理信息的方式。深度学习的主要优势在于,有可能设计出能够自动提取相关模式的神经网络,进而可以用来训练学习者。随着神经网络的最新进展,图像识别的准确性显著提高。例如, ImageNet 挑战赛向参赛者提供了来自 1000 个不同对象类别的 120 多万张图像,据报道,使用支持向量机 ( SVM )的最佳算法的错误率从 2010 年的 28%下降到 2014 年的 7%,使用了深度神经网络。

在这一章中,我们将快速浏览一下神经网络,从基本的构建模块,感知器,开始,逐渐引入更复杂的结构。

神经网络

第一个神经网络是在六十年代被引入的,它受到了生物神经网络的启发。神经网络的想法是绘制生物神经系统,即大脑如何处理信息。它由相互连接的神经元层组成。在计算机术语中,它们也被称为人工神经网络 ( )。对于计算机,需要训练才能让这个模型学习,就像人脑一样。大脑中的神经元在接收到来自附近相互连接的神经元的信号时被激活,这同样适用于人工神经网络。神经网络的最新进展已经证明,深度神经网络非常适合模式识别任务,因为它们能够自动提取有趣的特征并学习底层呈现。在这一节中,我们将刷新自己的基本结构和组件,从一个单一的感知器到深层网络。

感知器

感知器是基本的神经网络构建模块,也是最早的监督算法之一。它被定义为特征的总和,乘以相应的权重和偏差。当接收到输入信号时,它与指定的权重相乘。这些权重是为每个输入信号或输入定义的,并且在学习阶段不断调整权重。权重的调整取决于最后结果的误差。在与各自的权重相乘后,所有的输入与称为偏差的某个偏移值相加。偏差值也通过权重来调整。因此,它从随机权重和偏差开始,并且随着每次迭代,权重和偏差被调整,以便下一个结果向期望的输出移动。最后,最终结果被转换成输出信号。将所有这些加在一起的函数被称为和传递函数,它被输入到激活函数中。如果二元阶跃激活函数达到一个阈值,则输出为 1,否则为 0,这就给了我们一个二元分类器。下图显示了一个示意图:

训练感知器涉及相当简单的学习算法,该算法计算计算的输出值和正确的训练输出值之间的误差,并使用该误差来创建对权重的调整,从而实现梯度下降的形式。这种算法通常被称为 delta 规则

单层感知器不是很先进,非线性可分离函数,如 XOR,不能用它来建模。为了解决这个问题,引入了一种具有多个感知器的结构,称为多层感知器,也称为前馈神经网络

前馈神经网络

前馈神经网络是由几个感知器组成的人工神经网络,这些感知器按层组织,如下图所示:输入层、输出层以及一个或多个隐藏层。隐层与外界无关,故名。每一层感知器,也称为神经元,都与下一层的感知器直接相连,而两个神经元之间的连接则具有与感知器权重类似的权重。因此,一层中的所有感知器都与下一层中的感知器相连,信息被前馈到下一层。该图显示了一个具有四单元输入层,对应于长度4的特征向量的大小,四单元隐藏层,以及两单元输出层的网络,其中每个单元对应于一个类值:

前馈神经网络通过找到输入和输出值之间的关系来学习,这些值被多次输入到网络中。训练多层网络最流行的方法是反向传播。在反向传播中,计算的输出值与正确的值进行比较,其方式与 delta 规则中的方式相同。然后通过各种技术通过网络反馈该误差,调整每个连接的权重以减少误差值。使用网络输出值和原始输出值之间的平方差来计算误差。误差表明我们离原始输出值有多远。这个过程重复足够多的训练周期,直到误差低于某个阈值。

前馈神经网络可以有一个以上的隐藏层,其中每个附加的隐藏层在前面的层之上建立一个新的抽象。这通常会产生更精确的模型;然而,增加隐藏层的数量会导致两个已知的问题:

  • 消失梯度问题:随着隐藏层越来越多,反向传播的训练对于将信息传递给前面的层变得越来越没用,导致这些层的训练非常缓慢
  • 过拟合:模型与训练数据拟合得太好,在真实例子上表现不佳

让我们看看解决这些问题的其他一些网络结构。

自动编码器

自动编码器是一个前馈神经网络,旨在学习如何压缩原始数据集。它的目的是将输入复制到输出。因此,我们不会将要素映射到输入图层,也不会将标注映射到输出图层,而是将要素映射到输入图层和输出图层。隐藏图层中的单元数通常不同于输入图层中的单元数,这迫使网络扩展或减少原始要素的数量。这样,网络将学习重要的特征,同时有效地应用降维。

下图显示了一个示例网络。三单位输入层首先扩展为四单位层,然后压缩为单单位层。网络的另一端将单层单元恢复到四单元层,然后恢复到原始的三输入层:

一旦训练好网络,我们就可以像传统的图像处理一样,从左侧提取图像特征。它由编码器和解码器组成,其中编码器的工作是创建或隐藏一个或多个捕捉输入本质的层,解码器从层中重建输入。

自动编码器也可以组合成堆叠式自动编码器,如下图所示。首先,我们将讨论基本自动编码器中的隐藏层,如前所述。然后,我们将学习隐藏层(绿色圆圈)并重复这个过程,这实际上学习了一个更抽象的表达。我们可以多次重复这一过程,将原始特征转换成越来越小的维度。最后,我们将所有的隐藏层叠加到一个常规的前馈网络中,如图右上方所示:

受限玻尔兹曼机

受限玻尔兹曼机 ( RBM )是一种无向神经网络,也称为生成随机网络 ( GSNs ),可以学习其输入集合上的概率分布。顾名思义,它们起源于玻尔兹曼机器,这是一种在八十年代推出的递归神经网络。在玻尔兹曼机器中,每个节点或神经元都与所有其他节点相连,当节点计数增加时,这使得处理变得困难。受限意味着神经元必须形成两个完全连接的层,一个输入层和一个隐藏层,如下图所示:

与前馈网络不同,可见层和隐藏层之间的连接是无向的,因此这些值可以在可见到隐藏和隐藏到可见两个方向上传播。

训练 RBMs 基于对比散度算法,该算法使用类似于反向传播的梯度下降过程来更新权重,并且在马尔可夫链上应用 Gibbs 采样来估计梯度,即如何改变权重的方向。

RBM 也可以被堆叠起来创建一个被称为深度信念网络 ( DBNs )的类。在这种情况下,RBM 的隐藏图层充当 RBM 图层的可见图层,如下图所示:

在这种情况下,训练是递增的:一层一层地训练。

深度卷积网络

最近在图像识别基准测试中取得非常好结果的一种网络结构是卷积神经网络 ( CNN )或 ConvNet。CNN 是一种前馈神经网络,其构造方式模仿视觉皮层的行为,利用输入图像的 2D 结构,即表现出空间局部相关性的模式。它的工作原理是大脑如何回忆或记忆图像。作为人类,我们只根据特征来记忆图像。鉴于这些特征,我们的大脑将开始自己形成图像。在计算机中,考虑下图,该图显示了如何检测功能:

同样,从图像中检测到许多特征,如下图所示:

CNN 由许多卷积层和二次采样层组成,后面可选地是全连接层。下图显示了一个这样的例子。输入层读取图像中的所有像素,然后我们应用多个过滤器。在下图中,应用了四种不同的过滤器。每个过滤器被应用于原始图像;例如,6×6 过滤器的一个像素被计算为 6×6 平方的输入像素和相应的 6×6 权重的加权和。这有效地引入了类似于标准图像处理的滤波器,例如平滑、相关、边缘检测等等。产生的图像被称为特征图。在下图的示例中,我们有四个特征映射,每个过滤器一个。

下一层是子采样层,它减小了输入的大小。每个特征图通常在 2×2(对于大图像高达 5×5)的连续区域上用平均值或最大值池进行二次抽样。例如,如果特征图大小为 16×16,子采样区域为 2×2,则减小的特征图大小为 8×8,其中通过计算最大值、最小值、平均值或一些其他函数将 4 个像素(2×2 的正方形)组合成单个像素:

网络可能包含几个连续的卷积和子采样层,如上图所示。特定的特征地图被连接到下一个简化/回旋的特征地图,而同一层的特征地图彼此不连接。

在最后一个子采样或卷积层之后,通常有一个完全连接的层,与标准多层神经网络中的层相同,表示目标数据。

使用修改的反向传播算法来训练 CNN,该算法考虑了子采样层,并且基于应用该滤波器的所有值来更新卷积滤波器权重。

一些好的 CNN 设计可以在 ImageNet 竞赛结果页面找到:www.image-net.org/。一个例子是 AlexNet ,它在 A. Krizhevsky 等人ImageNet 分类与深度协变神经网络论文中有所描述。

这就结束了我们对主要神经网络结构的回顾。在下一节中,我们将继续讨论实际的实现。

图像分类

在本节中,我们将讨论如何使用 Deeplearning4j 库实现一些神经网络结构。让我们开始吧。

深度学习 4j

正如我们在第二章、中讨论的,用于机器学习的 Java 库和平台,Deeplearning4j 是一个开源的、分布式的 Java 和 Scala 深度学习项目。Deeplearning4j 依靠 Spark 和 Hadoop for MapReduce,并行训练模型,并在一个中心模型中迭代平均它们产生的参数。详细的库总结在第二章、用于机器学习的 Java 库和平台中给出。

获取 DL4J

获取 Deeplearning4j 最便捷的方式是通过 Maven 资源库:

  1. 启动一个新的 Eclipse 项目并选择 Maven 项目,如下面的屏幕截图所示:

  1. 打开pom.xml文件,并在<dependencies>部分下添加以下依赖项:
<dependency> 
    <groupId>org.deeplearning4j</groupId> 
    <artifactId>deeplearning4j-nlp</artifactId> 
   <version>${dl4j.version}</version> 
</dependency> 

<dependency> 
    <groupId>org.deeplearning4j</groupId> 
    <artifactId>deeplearning4j-core</artifactId> 
    <version>${dl4j.version}</version> 
</dependency> 
  1. 最后,右键单击 Project,选择 Maven,然后选择 Update project。

MNIST 数据集

最著名的数据集之一是 MNIST 数据集,它由手写数字组成,如下图所示。该数据集由 60,000 幅训练图像和 10,000 幅测试图像组成:

该数据集通常用于图像识别问题,以对算法进行基准测试。记录的最差错误率是 12%,没有预处理,并且在单层神经网络中使用 SVM。目前截至 2016 年,错误率最低的只有 0.21%,使用的是 DropConnect 神经网络,其次是深度卷积网络 0.23%,深度前馈网络 0.35%。

现在,让我们看看如何加载数据集。

加载数据

Deeplearning4j 提供了现成的 MNIST 数据集加载器。加载程序被初始化为DataSetIterator。首先让我们导入DataSetIterator类和所有受支持的数据集,它们是impl包的一部分,例如,iris、MNIST 和其他:

import org.deeplearning4j.datasets.iterator.DataSetIterator; 
import org.deeplearning4j.datasets.iterator.impl.*; 

接下来,我们将定义一些常数,例如,图像由 28 x 28 像素组成,有 10 个目标类和 60,000 个样本。我们将初始化一个新的MnistDataSetIterator类,它将下载数据集及其标签。这些参数是迭代批次大小、样本总数以及数据集是否应该二进制化:

int numRows = 28; 
int numColumns = 28; 
int outputNum = 10;
int numSamples = 60000;
int batchSize = 100;
int iterations = 10;
int seed = 123;
DataSetIterator iter = new MnistDataSetIterator(batchSize, 
numSamples,true);  

拥有一个已经实现的数据导入器确实很方便,但是它不能处理您的数据。让我们快速看一下它是如何实现的,以及需要修改什么来支持您的数据集。如果您急于开始实现神经网络,您可以安全地跳过本节的其余部分,并在需要导入您自己的数据时返回。

To load the custom data, you'll need to implement two classes: DataSetIterator, which holds all of the information about the dataset, and BaseDataFetcher, which actually pulls the data either from a file, database, or the web. Sample implementations are available on GitHub at github.com/deeplearning4j/deeplearning4j/tree/master/deeplearning4j-core/src/main/java/org/deeplearning4j/datasets/iterator/impl.  Another option is to use the Canova library, which was developed by the same authors, at deeplearning4j.org/canovadoc/.

建筑模型

在这一节中,我们将讨论如何建立一个实际的神经网络模型。我们将从一个基本的单层神经网络开始,建立一个基准,并讨论基本操作。稍后,我们将使用 DBN 和多层卷积网络来改进这个初始结果。

构建单层回归模型

我们先从建立基于 softmax 激活函数的单层回归模型开始,如下图所示。由于我们有一个单层,输入到神经网络的将是所有的图形像素,即 28×28 =748 个神经元。输出神经元的数量为 10 ,每个数字一个。网络层完全连接,如下图所示:

神经网络通过NeuralNetConfiguration.Builder()对象定义如下:

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder() 

我们将定义梯度搜索的参数,以便用共轭梯度优化算法执行迭代。momentum参数决定了优化算法收敛到局部最优的速度。momentum越高,训练越快;但是更高的速度会降低模型的准确性:

.seed(seed) 
.gradientNormalization(GradientNormalization.ClipElementWiseAbsolu
   teValue) 
   .gradientNormalizationThreshold(1.0) 
   .iterations(iterations) 
   .momentum(0.5) 
   .momentumAfter(Collections.singletonMap(3, 0.9)) 
   .optimizationAlgo(OptimizationAlgorithm.CONJUGATE_GRADIENT) 

接下来,我们将指定网络有一层,并定义误差函数NEGATIVELOGLIKELIHOOD、内部感知器激活函数softmax,以及对应于总 图像像素和目标变量数量的输入和输出层数,如以下代码块所示:

.list(1) 
.layer(0, new  
OutputLayer.Builder(LossFunction.NEGATIVELOGLIKELIHOOD) 
.activation("softmax") 
.nIn(numRows*numColumns).nOut(outputNum).build()) 

最后,我们将网络设置为pretrain,禁用反向传播,实际构建未训练的网络结构:

   .pretrain(true).backprop(false) 
   .build(); 

一旦定义了网络结构,我们就可以用它来初始化一个新的MultiLayerNetwork,如下:

MultiLayerNetwork model = new MultiLayerNetwork(conf); 
model.init(); 

接下来,我们将通过调用setListeners方法将模型指向训练数据,如下所示:

model.setListeners(Collections.singletonList((IterationListener) 
   new ScoreIterationListener(listenerFreq))); 

我们还将调用fit(int)方法来触发端到端网络训练:

model.fit(iter);  

为了评估模型,我们将初始化一个新的Evaluation对象,它将存储批处理结果:

Evaluation eval = new Evaluation(outputNum); 

然后,我们可以批量迭代数据集,以便将内存消耗保持在合理的水平,并将结果存储在一个eval对象中:

DataSetIterator testIter = new MnistDataSetIterator(100,10000); 
while(testIter.hasNext()) { 
    DataSet testMnist = testIter.next(); 
    INDArray predict2 =  
    model.output(testMnist.getFeatureMatrix()); 
    eval.eval(testMnist.getLabels(), predict2); 
} 

最后,我们可以通过调用stats()函数得到结果:

log.info(eval.stats()); 

一个基本的单层模型可以达到以下精度:

    Accuracy:  0.8945 
    Precision: 0.8985
    Recall:    0.8922
    F1 Score:  0.8953

在 MNIST 数据集上获得 89.22%的准确率,即 10.88%的错误率是非常糟糕的。我们将通过使用受限玻尔兹曼机器和多层卷积网络,从简单的一层网络到适度复杂的深度信念网络来改进这一点。

建立一个深度的信念网络

在这一部分,我们将基于 RBM 构建一个深度信念网络(DBN),如下图所示。该网络由四层组成。第一层将 748 输入减少到 500 神经元,然后到 250 ,接着是 200 ,最后到最后的 10 目标值:

由于代码与前面的示例相同,我们来看看如何配置这样一个网络:

MultiLayerConfiguration conf = new 
   NeuralNetConfiguration.Builder() 

我们将定义梯度优化算法,如下面的代码所示:

    .seed(seed) 
    .gradientNormalization( 
    GradientNormalization.ClipElementWiseAbsoluteValue) 
    .gradientNormalizationThreshold(1.0) 
    .iterations(iterations) 
    .momentum(0.5) 
    .momentumAfter(Collections.singletonMap(3, 0.9)) 
    .optimizationAlgo(OptimizationAlgorithm.CONJUGATE_GRADIENT) 

我们还将指定我们的网络将有四层:

   .list(4) 

第一层的输入将是748神经元,输出将是500神经元。我们将使用均方根误差交叉熵和 Xavier 算法,通过基于输入和输出神经元的数量自动确定初始化的规模来初始化权重,如下所示:

.layer(0, new RBM.Builder() 
.nIn(numRows*numColumns) 
.nOut(500)          
.weightInit(WeightInit.XAVIER) 
.lossFunction(LossFunction.RMSE_XENT) 
.visibleUnit(RBM.VisibleUnit.BINARY) 
.hiddenUnit(RBM.HiddenUnit.BINARY) 
.build()) 

接下来的两层将具有相同的参数,除了输入和输出神经元的数量:

.layer(1, new RBM.Builder() 
.nIn(500) 
.nOut(250) 
.weightInit(WeightInit.XAVIER) 
.lossFunction(LossFunction.RMSE_XENT) 
.visibleUnit(RBM.VisibleUnit.BINARY) 
.hiddenUnit(RBM.HiddenUnit.BINARY) 
.build()) 
.layer(2, new RBM.Builder() 
.nIn(250) 
.nOut(200) 
.weightInit(WeightInit.XAVIER) 
.lossFunction(LossFunction.RMSE_XENT) 
.visibleUnit(RBM.VisibleUnit.BINARY) 
.hiddenUnit(RBM.HiddenUnit.BINARY) 
.build()) 

现在,最后一层将把神经元映射到输出,我们将使用softmax激活函数,如下所示:

.layer(3, new OutputLayer.Builder() 
.nIn(200) 
.nOut(outputNum) 
.lossFunction(LossFunction.NEGATIVELOGLIKELIHOOD) 
.activation("softmax") 
.build()) 
.pretrain(true).backprop(false) 
.build(); 

其余的训练和评估与单层网络示例中的相同。请注意,与单层网络相比,训练深层网络可能需要更多的时间。准确率应该在 93%左右。

现在,让我们来看看另一个深层网络。

构建多层卷积网络

在最后一个例子中,我们将讨论如何构建卷积网络,如下图所示。该网络将由七层组成。首先,我们将使用最大池重复两对卷积和子采样层。最后的二次采样层然后连接到密集连接的前馈神经网络,在最后三层中分别由 120 个神经元、84 个神经元和 10 个神经元组成。这样的网络有效地形成了完整的图像识别管道,其中前四层对应于特征提取,后三层对应于学习模型:

网络配置如我们之前所做的那样初始化:

MultiLayerConfiguration.Builder conf = new 
   NeuralNetConfiguration.Builder() 

我们将指定梯度下降算法及其参数,如下所示:

.seed(seed) 
.iterations(iterations) 
.activation("sigmoid") 
.weightInit(WeightInit.DISTRIBUTION) 
.dist(new NormalDistribution(0.0, 0.01)) 
.learningRate(1e-3) 
.learningRateScoreBasedDecayRate(1e-1) 
.optimizationAlgo( 
OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT) 

我们还将指定七个网络层,如下所示:

.list(7) 

第一卷积层的输入是完整的图像,而输出是六个特征图。卷积层将应用 5×5 过滤器,结果将存储在 1×1 单元中:

.layer(0, new ConvolutionLayer.Builder( 
    new int[]{5, 5}, new int[]{1, 1}) 
    .name("cnn1") 
    .nIn(numRows*numColumns) 
    .nOut(6) 
    .build()) 

第二层是二次采样层,它将占用 2 x 2 区域,并将最大结果存储在 2 x 2 元素中:

.layer(1, new SubsamplingLayer.Builder( 
SubsamplingLayer.PoolingType.MAX,  
new int[]{2, 2}, new int[]{2, 2}) 
.name("maxpool1") 
.build()) 

接下来的两层将重复前两层:

.layer(2, new ConvolutionLayer.Builder(new int[]{5, 5}, new 
   int[]{1, 1}) 
    .name("cnn2") 
    .nOut(16) 
    .biasInit(1) 
    .build()) 
.layer(3, new SubsamplingLayer.Builder
   (SubsamplingLayer.PoolingType.MAX, new 
   int[]{2, 2}, new int[]{2, 2}) 
    .name("maxpool2") 
    .build()) 

现在,我们将把二次采样层的输出连接到由120神经元组成的密集前馈网络,然后通过另一层连接到84神经元,如下所示:

.layer(4, new DenseLayer.Builder() 
    .name("ffn1") 
    .nOut(120) 
    .build()) 
.layer(5, new DenseLayer.Builder() 
    .name("ffn2") 
    .nOut(84) 
    .build()) 

最后一层连接84神经元和10输出神经元:

.layer(6, new OutputLayer.Builder
   (LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD) 
    .name("output") 
    .nOut(outputNum) 
    .activation("softmax") // radial basis function required 
    .build()) 
.backprop(true) 
.pretrain(false) 
.cnnInputSize(numRows,numColumns,1); 

为了训练这种结构,我们可以重用我们在前两个例子中开发的代码。同样,培训可能需要一些时间。网络准确率应该在 98%左右。

Since model training significantly relies on linear algebra, training can be significantly sped up by using a graphics processing unit (GPU) for an order of magnitude. As the GPU backend, at the time of writing this book, is undergoing a rewrite, please check the latest documentation at deeplearning4j.org/documentation.

正如我们在不同的例子中看到的,越来越复杂的神经网络允许我们自动提取相关特征,从而完全避免了传统的图像处理。然而,我们为此付出的代价是增加处理时间和大量的学习示例,以使这种方法有效。

摘要

在本章中,我们讨论了如何识别图像中的模式,以便通过涵盖深度学习的基本原则来区分不同的类别,并讨论了如何使用 Deeplearning4j 库来实现它们。我们从更新基本的神经网络结构开始,并讨论了如何实现它们来解决手写数字识别问题。

在下一章,我们将进一步研究模式;然而,代替图像中的模式,我们将处理具有时间依赖性的模式,这可以在传感器数据中找到。

九、利用手机传感器进行活动识别

虽然上一章关注的是图像中的模式识别,但本章讨论的是识别传感器数据中的模式,与图像不同,传感器数据具有时间依赖性。我们将讨论如何使用手机惯性传感器识别日常活动,如走路、坐着和跑步。本章还提供了相关研究的参考,并强调了活动识别社区的最佳实践。

本章涵盖的主题将包括以下内容:

  • 介绍活动识别,涵盖手机传感器和活动识别渠道
  • 从移动设备收集传感器数据
  • 讨论活动分类和模型评估
  • 部署活动识别模型

介绍活动识别

活动识别是行为分析的基础步骤,涉及健康的生活方式、健康跟踪、远程协助、安全应用、老年人护理等。活动识别将来自加速度计、陀螺仪、压力传感器和 GPS 位置等传感器的低级传感器数据转换为行为原语的高级描述。

在大多数情况下,这些都是基本的活动,例如,走、坐、躺、跳等等,如下图所示,或者它们可以是更复杂的行为,例如上班、准备早餐和购物:

在本章中,我们将讨论如何将活动识别功能添加到移动应用程序中。我们将首先看看活动识别问题是什么样的,我们需要收集什么样的数据,主要的挑战是什么,以及如何解决它们。

稍后,我们将通过一个示例来了解如何在一个 Android 应用程序中实际实现活动识别,包括数据收集、数据转换和构建分类器。

开始吧!

手机传感器

我们先来回顾一下手机传感器都有哪些种类,报的是什么。大多数智能设备现在都配备了几个内置传感器,可以测量周围环境的运动、位置、方向和条件。由于传感器提供高精度、高频率和高准确度的测量,因此有可能重建复杂的用户动作、手势和移动。传感器经常被结合到各种应用中;例如,陀螺仪读数用于操纵游戏中的对象,GPS 数据用于定位用户,加速度计数据用于推断用户正在进行的活动,例如骑自行车、跑步或步行。

下图显示了传感器能够检测到的交互类型的几个示例:

移动电话传感器可分为以下三大类:

  • **运动传感器:**该传感器测量沿三个垂直轴的加速度和旋转力。这类传感器的例子包括加速度计、重力传感器和陀螺仪。
  • **环境传感器:**该传感器测量各种环境参数,如光照、空气温度、压力和湿度。这一类包括气压计、光度计和温度计。
  • **位置传感器:**该传感器测量设备的物理位置。这一类包括方位传感器和磁力计。

有关不同移动平台的更多详细说明,请访问以下链接:

在这一章中,我们将只使用 Android 的传感器框架。

活动识别管道

正如我们在前面章节中看到的,对多维时间序列传感器数据进行分类本质上比对传统名义数据进行分类更复杂。首先,每个观测值在时间上与前一个和后一个观测值相关,这使得很难只对一组观测值进行简单的分类。第二,传感器在不同时间点获得的数据是随机的,也就是说,由于传感器噪声、环境干扰和许多其他因素的影响,这些数据是不可预测的。此外,一项活动可以由以不同方式执行的各种子活动组成,每个人执行活动的方式稍有不同,这导致了较高的组内差异。最后,所有这些原因使得活动识别模型不精确,导致新数据经常被错误分类。活动识别分类器的一个非常理想的特性是确保所识别的活动序列的连续性和一致性。

为了应对这些挑战,活动识别应用于管道,如下图所示:

第一步,我们尽可能多地衰减噪声,例如,通过降低传感器采样速率、移除异常值、应用高通或低通滤波器等。在下一阶段,我们构建一个特征向量。例如,我们通过应用离散傅立叶变换 ( DFT )将传感器数据从时域转换到频域。DFT 是一种将样本列表作为输入并返回按频率排序的正弦系数列表的方法。它们表示原始样本列表中存在的频率组合。

A gentle introduction to the Fourier transform was written by Pete Bevelacqua at www.thefouriertransform.com/. If you want to get a more technical and theoretical background on the Fourier transform, take a look at the eighth and ninth lectures in the class by Robert Gallager and Lizhong Zheng at this MIT open course: theopenacademy.com/content/principles-digital-communication.

接下来,基于特征向量和训练数据集,我们可以构建一个活动识别模型,为每个观察分配一个原子动作。因此,对于每个新的传感器读数,模型将输出最可能的活动标签。然而,模型也会出错。因此,最后一个阶段通过删除实际上不可能发生的转换来平滑活动之间的转换;例如,躺-站-躺活动之间的转换在少于半秒的时间内发生在物理上是不可行的,因此,活动之间的这种转换被平滑为躺-躺-躺。

活动识别模型是用监督学习方法构建的,该方法包括训练和分类步骤。在训练步骤中,提供一组标记数据来训练模型。第二步是由训练好的模型给新的看不见的数据分配一个标签。两个阶段中的数据必须用相同的工具集进行预处理,例如过滤和特征向量计算。

后处理阶段,即虚假活动去除,也可以是模型本身,因此也需要学习步骤。在这种情况下,预处理步骤还包括活动识别,这使得分类器的这种排列成为元学习问题。为了避免过度拟合,重要的是用于训练后处理阶段的数据集不同于用于训练活动识别模型的数据集。

这个计划

该计划包括培训阶段和部署阶段。培训阶段可归结为以下步骤:

  1. 安装 Android Studio,导入MyRunsDataCollector.zip
  2. 在你的 Android 手机上加载应用程序。
  3. 收集您的数据,例如站立、行走和跑步,并将数据转换为由 FFT 组成的特征向量。不要慌;FFT 等低级信号处理函数将不会从头开始编写,因为我们将使用现有代码来完成。这些数据将被保存在手机上一个名为features.arff的文件中。
  4. 使用导出的数据创建和评估活动识别分类器,并实施过滤器以消除虚假的活动转换。
  5. 将分类器插回到移动应用程序中。

如果你没有 Android 手机,或者如果你想跳过所有与移动应用程序相关的步骤,只需抓取位于data/features.arff的收集数据集,并直接跳转到构建分类器部分。

从手机收集数据

本节描述了该计划的前三个步骤。如果您想直接处理数据,您可以跳过这一部分,继续到构建分类器部分。该应用程序实现了收集不同活动类别(例如,站立、行走、跑步等)的传感器数据的基本要素。

让我们从准备 Android 开发环境开始。如果您已经安装了它,请跳到加载数据采集器部分。

安装 Android Studio

Android Studio 是 Android 平台的开发环境。我们将快速回顾在手机上启动应用程序所需的安装步骤和基本配置。关于 Android 开发的更详细的介绍,我推荐一本入门书,Packt 出版社的 Kyle Mew 的《Android 5 编程示例》。

developer.android.com/studio/为开发者下载最新的 Android Studio,并按照 developer.android.com/sdk/install… pkg =工作室](developer.android.com/sdk/install… 10 分钟,占用大约 0.5 GB 的空间。

按照说明选择您喜欢的安装选项,最后单击 Finish 开始安装,如下面的屏幕截图所示:

加载数据采集器

首先从 GitHub 上抓取MyRunsDataCollector的源代码。安装 Android Studio 后,选择打开一个现有的 Android Studio 项目选项,如下图所示,并选择MyRunsDataCollector文件夹。这将把项目导入 Android Studio:

项目导入完成后,您应该能够看到项目文件结构,如下图所示。采集器由CollectorActivity.javaGlobals.javaSensorsService.java组成。该项目还显示了FFT.java实现低电平信号处理:

myrunscollector包包含以下类:

  • Globals.java:定义全局常量,如活动标签和 id,以及数据文件名。
  • CollectorActivity.java:实现用户界面动作,也就是按下特定按钮时发生的事情。
  • 这实现了一个收集数据、计算特征向量的服务,我们将在下面的章节中讨论,并将数据存储到手机上的一个文件中。

我们要解决的下一个问题是如何设计功能。

特征抽出

找到一个人的活动的适当表示可能是活动识别中最具挑战性的部分。行为需要用简单和通用的特征来表示,以便使用这些特征的模型也将是通用的,并且在不同于学习集中的行为上工作良好。

事实上,在一个训练集中设计特定于捕获的观察的特征并不困难;这样的功能在他们身上会很好用。然而,由于训练集仅捕获人类行为的整个范围的一部分,过于具体的特征可能会在一般行为上失败:

让我们看看这是如何在MyRunsDataCollector中实现的。当应用程序启动时,一个名为onSensorChanged()的方法获取带有特定时间戳的三个加速度计传感器读数( xyz ),并根据传感器读数计算大小。该方法在计算 FFT 系数之前缓冲多达 64 个连续的幅度标记。

现在,让我们继续实际的数据收集。

收集培训数据

我们现在可以使用收集器来收集活动识别的训练数据。默认情况下,收集器支持三种活动:站立、行走和跑步,如下图所示。

您可以选择一个活动,即目标类值,并通过单击 START COLLECTING 按钮开始记录数据。确保每项活动至少记录三分钟;例如,如果选择了步行活动,请按开始收集并步行至少三分钟。活动结束时,按停止收集。对每个活动重复这一步骤。

您还可以收集涉及这些活动的不同场景,例如,在厨房里行走、在外面行走、排队行走等等。通过这样做,您将拥有每个活动类的更多数据和一个更好的分类器。有道理,对吧?数据越多,分类器就越不混乱。如果只有少量数据,将会出现过度拟合,分类器会混淆类别-站立与行走,行走与跑步,等等。然而,数据越多,他们越不会感到困惑。当你调试的时候,你可能每个类收集不到三分钟的数据,但是对于你最终的成品来说,数据越多越好。多个记录实例将简单地累积在同一个文件中。

请注意,删除数据按钮会删除存储在手机文件中的数据。如果你想重新开始,在开始前点击删除数据;否则,新收集的数据将被附加到文件的末尾:

收集器实现了前面几节中讨论的图表:它收集加速度计样本,计算幅度,使用FFT.java类计算系数,并产生特征向量。然后,数据被存储在 Weka 格式的features.arff文件中。特征向量的数量会根据您收集的数据量而有所不同。收集数据的时间越长,积累的特征向量就越多。

一旦您停止使用收集器工具收集训练数据,我们就需要获取数据来继续工作流。我们可以使用 Android 设备监视器中的文件浏览器从手机上传features.arff文件,并将其存储在电脑上。您可以通过单击 Android robot 图标来访问您的 Android 设备监视器,如下图所示:

通过在左侧选择您的设备,您的手机存储内容将显示在右侧。浏览mnt/shell/emulated/Android/data/edu.dartmouth.cs.myrunscollector/files/features.arff,如下图所示:

要将此文件上传到您的计算机,您需要选择该文件(突出显示)并单击上传。

现在,我们准备构建一个分类器。

构建分类器

一旦传感器样本被表示为特征向量并分配了类别,就可以应用标准技术进行监督分类,包括特征选择、特征离散化、模型学习、k-fold 交叉验证等。本章不会深入研究机器学习算法的细节。可以应用任何支持数字特征的算法,包括支持向量机、随机森林、AdaBoost、决策树、神经网络、多层感知器等等。

因此,让我们从一个基本的开始:决策树。在这里,我们将加载数据集,构建 set class 属性,构建决策树模型,并输出模型:

String databasePath = "/Users/bostjan/Dropbox/ML Java Book/book/datasets/chap9/features.arff"; 

// Load the data in arff format 
Instances data = new Instances(new BufferedReader(new 
   FileReader(databasePath))); 

// Set class the last attribute as class 
data.setClassIndex(data.numAttributes() - 1); 

// Build a basic decision tree model 
String[] options = new String[]{}; 
J48 model = new J48(); 
model.setOptions(options); 
model.buildClassifier(data); 

// Output decision tree 
System.out.println("Decision tree model:\n"+model); 

该算法首先输出模型,如下所示:

    Decision tree model:
    J48 pruned tree
    ------------------

    max <= 10.353474
    |   fft_coef_0000 <= 38.193106: standing (46.0)
    |   fft_coef_0000 > 38.193106
    |   |   fft_coef_0012 <= 1.817792: walking (77.0/1.0)
    |   |   fft_coef_0012 > 1.817792
    |   |   |   max <= 4.573082: running (4.0/1.0)
    |   |   |   max > 4.573082: walking (24.0/2.0)
    max > 10.353474: running (93.0)

    Number of Leaves  : 5

    Size of the tree : 9

该树非常简单,看起来很准确,因为终端节点中的多数类分布非常高。让我们运行一个基本分类器评估来验证结果,如下所示:

// Check accuracy of model using 10-fold cross-validation 
Evaluation eval = new Evaluation(data); 
eval.crossValidateModel(model, data, 10, new Random(1), new 
   String[] {}); 
System.out.println("Model performance:\n"+ 
   eval.toSummaryString()); 

这将输出以下模型性能:

    Correctly Classified Instances         226               92.623  %
    Incorrectly Classified Instances        18                7.377  %
    Kappa statistic                          0.8839
    Mean absolute error                      0.0421
    Root mean squared error                  0.1897
    Relative absolute error                 13.1828 %
    Root relative squared error             47.519  %
    Coverage of cases (0.95 level)          93.0328 %
    Mean rel. region size (0.95 level)      27.8689 %
    Total Number of Instances              244     

分类准确率得分非常高,92.62%,这是一个惊人的结果。结果这么好的一个重要原因在于我们的评价设计。我在这里的意思是:连续的实例彼此非常相似,所以如果我们在 10 倍交叉验证期间随机地将它们分开,那么我们很有可能在训练和测试中使用几乎相同的实例;因此,直接的 k 倍交叉验证产生了对模型性能的乐观估计。

更好的方法是使用对应于不同测量集甚至不同人的折叠。例如,我们可以使用应用程序收集五个人的学习数据。然后,运行 k 人交叉验证是有意义的,其中模型在四个人身上训练,在第五个人身上测试。对每个人重复该过程,并对结果进行平均。这将使我们对模型性能有一个更加真实的估计。

抛开评价评论,让我们看看如何处理分类器错误。

减少杂散跃迁

在活动识别管道的最后,我们希望确保分类不会太不稳定,也就是说,我们不希望活动每毫秒都发生变化。一个基本的方法是设计一个忽略活动序列中快速变化的滤波器。

我们构建了一个过滤器,它能记住最后的窗口活动并返回最频繁的活动。如果有多个得分相同的活动,它将返回最近的一个。

首先,我们创建一个新的SpuriousActivityRemoval类,它将保存一个活动列表和window参数:

class SpuriousActivityRemoval{ 

  List<Object> last; 
  int window; 

  public SpuriousActivityRemoval(int window){ 
    this.last = new ArrayList<Object>(); 
    this.window = window; 
  } 

接下来,我们创建Object filter(Object)方法,该方法将接受一个活动并返回一个经过过滤的活动。该方法首先检查我们是否有足够的观察值。如果没有,它只存储观察值并返回相同的值,如下面的代码所示:

  public Object filter(Object obj){ 
    if(last.size() < window){ 
      last.add(obj); 
      return obj; 
  } 

如果我们已经收集了window观察值,我们只需返回最频繁的观察值,删除最早的观察值,并插入新的观察值:

    Object o = getMostFrequentElement(last); 
    last.add(obj); 
    last.remove(0); 
    return o; 
  } 

这里缺少的是一个从对象列表中返回最频繁元素的函数。我们使用哈希映射来实现这一点,如下所示:

  private Object getMostFrequentElement(List<Object> list){ 

    HashMap<String, Integer> objectCounts = new HashMap<String, 
       Integer>(); 
    Integer frequntCount = 0; 
    Object frequentObject = null; 

现在,我们遍历列表中的所有元素,将每个唯一的元素插入到一个哈希映射中,或者更新它的计数器(如果它已经在哈希映射中的话)。在循环结束时,我们存储目前为止找到的最频繁的元素,如下所示:

    for(Object obj : list){ 
      String key = obj.toString(); 
      Integer count = objectCounts.get(key); 
      if(count == null){ 
        count = 0; 
      } 
      objectCounts.put(key, ++count); 

      if(count >= frequntCount){ 
        frequntCount = count; 
        frequentObject = obj; 
      } 
    } 

    return frequentObject; 
  } 

} 

让我们运行一个简单的例子:

String[] activities = new String[]{"Walk", "Walk", "Walk", "Run", 
   "Walk", "Run", "Run", "Sit", "Sit", "Sit"}; 
SpuriousActivityRemoval dlpFilter = new 
   SpuriousActivityRemoval(3); 
for(String str : activities){ 
  System.out.println(str +" -> "+ dlpFilter.filter(str)); 
} 

该示例输出以下活动:

    Walk -> Walk
    Walk -> Walk
    Walk -> Walk
    Run -> Walk
    Walk -> Walk
    Run -> Walk
    Run -> Run
    Sit -> Run
    Sit -> Run
    Sit -> Sit

结果是一系列连续的活动,也就是说,我们没有快速的变化。这增加了一些延迟,但除非这对应用程序绝对重要,否则这是可以接受的。

可以通过将由分类器识别的 n 个先前活动附加到特征向量来增强活动识别。附加先前活动的危险在于,机器学习算法可能知道当前活动总是与先前活动相同,因为这将是经常的情况。该问题可以通过具有两个分类器 A 和 B 来解决:分类器 B 的属性向量包含由分类器 A 识别的 n 个先前活动。分类器 A 的属性向量不包含任何先前活动。这样,即使 B 对之前的活动赋予了很大的权重,A 认可的之前的活动也会因为 A 没有背负 B 的惯性而改变。

剩下要做的就是将分类器嵌入并过滤到我们的移动应用程序中。

将分类器插入移动应用程序

有两种方法可以将分类器集成到移动应用程序中。第一个包括以 Weka 格式导出模型,在我们的移动应用程序中使用 Weka 库作为依赖项,加载模型,等等。该过程与我们在第三章、中看到的示例相同——分类、回归和聚类。第二种方法更加轻量级:我们将模型导出为源代码,例如,我们创建一个实现决策树分类器的类。然后,我们可以简单地将源代码复制并粘贴到我们的移动应用程序中,甚至不需要导入任何 Weka 依赖项。

幸运的是,一些 Weka 模型可以通过toSource(String)函数轻松导出到源代码:

// Output source code implementing the decision tree 
System.out.println("Source code:\n" +  
  model.toSource("ActivityRecognitionEngine")); 

这将输出一个与我们的模型相对应的ActivityRecognitionEngine类。现在,让我们仔细看看输出代码:

class ActivityRecognitionEngine { 

  public static double classify(Object[] i) 
    throws Exception { 

    double p = Double.NaN; 
    p = ActivityRecognitionEngine.N17a7cec20(i); 
    return p; 
  } 
  static double N17a7cec20(Object []i) { 
    double p = Double.NaN; 
    if (i[64] == null) { 
      p = 1; 
    } else if (((Double) i[64]).doubleValue() <= 10.353474) { 
    p = ActivityRecognitionEngine.N65b3120a1(i); 
    } else if (((Double) i[64]).doubleValue() > 10.353474) { 
      p = 2; 
    }  
    return p; 
  } 
... 

输出的ActivityRecognitionEngine类实现了我们之前讨论过的决策树。机器生成的函数名,如N17a7cec20(Object []),对应决策树节点。分类器可以通过classify(Object[])方法调用,在这里我们应该传递一个通过与我们在前面章节中讨论的相同过程获得的特征向量。像往常一样,它返回一个double,表示一个类标签索引。

摘要

在本章中,我们讨论了如何为移动应用程序实现一个活动识别模型。我们研究了完整的过程,包括数据收集、特征提取、模型构建、评估和模型部署。

在下一章,我们将继续讨论另一个针对文本分析的 Java 库:Mallet。

十、基于 Mallet 主题建模的文本挖掘和垃圾邮件检测

在这一章中,我们将首先讨论什么是文本挖掘,它能够提供什么样的分析,以及为什么你可能想在你的应用程序中使用它。然后我们将讨论如何使用 Mallet ,一个用于自然语言处理的 Java 库,包括数据导入和文本预处理。之后,我们将研究两个文本挖掘应用程序:主题建模,在这里我们将讨论如何使用文本挖掘来识别在文本文档中发现的主题,而无需单独阅读它们;以及垃圾邮件检测,在这里我们将讨论如何自动将文本文档分类。

本章将涵盖以下主题:

  • 文本挖掘简介
  • 安装和使用木槌
  • 主题建模
  • 垃圾邮件检测

文本挖掘简介

文本挖掘或文本分析是指从文本文档中自动提取高质量信息的过程,这些文档通常用自然语言编写,其中高质量信息被认为是相关的、新颖的和有趣的。

虽然典型的文本分析应用程序用于扫描一组文档以生成搜索索引,但文本挖掘可以用于许多其他应用程序,包括特定领域的文本分类;自动组织一组文档的文本聚类;情感分析以识别和提取文档中的主观信息;能够从文档中识别人、地点、组织和其他实体的概念或实体提取;文档摘要自动提供原始文档中最重要的点;和学习命名实体之间的关系。

基于统计模式挖掘的过程通常包括以下步骤:

  1. 信息检索和提取
  2. 将非结构化文本数据转换成结构化数据;例如,解析、去除干扰词、词法分析、计算词频以及导出语言特征
  3. 从结构化数据和标记或注释中发现模式
  4. 结果的评估和解释

在本章的后面,我们将研究两个应用领域:主题建模和文本分类。让我们看看他们带来了什么。

主题建模

主题建模是一种无人监督的技术,如果您需要分析一个大型的文本文档存档,并且希望了解存档中包含的内容,而不需要亲自阅读每一个文档,那么主题建模可能会很有用。文本文档可以是博客帖子、电子邮件、推文、文档、书籍章节、日记条目等等。主题建模在文本语料库中寻找模式;更准确地说,它将主题识别为以统计上有意义的方式出现的单词列表。最著名的算法是潜在狄利克雷分配 ( LDA ),它假设作者通过从可能的词篮中选择词来组成一段文本,其中每个词篮对应一个主题。使用这个假设,就有可能将文本数学地分解成单词最可能出现的篮子。然后,该算法迭代这个过程,直到它收敛到单词在篮子中最可能的分布,我们称之为主题

例如,如果我们对一系列新闻文章使用主题建模,该算法将返回最有可能包含这些主题的主题和关键字的列表。以新闻文章为例,该列表可能如下所示:

  • 赢家,进球,足球,得分,第一名
  • 公司、股票、银行、信用、商业
  • 选举,对手,总统,辩论,即将到来

通过查看关键字,我们可以识别出新闻文章关注的是体育、商业、即将到来的选举等等。在本章的后面,我们将学习如何使用新闻文章的例子来实现主题建模。

文本分类

在文本分类或文本分类中,目标是根据文本文档的内容将其分配到一个或多个类别或范畴,这些类别或范畴往往是更一般的主题领域,例如车辆或宠物。这样的一般类称为主题,分类任务则称为文本分类文本分类主题分类主题定位。虽然可以根据其他属性(如文档类型、作者和出版年份)对文档进行分类,但本章只关注文档内容。文本分类的示例包括以下组件:

  • 电子邮件、用户评论、网页等中的垃圾邮件检测
  • 色情内容的检测
  • 情感检测,自动将产品或服务评论分为正面或负面
  • 根据内容分类电子邮件
  • 特定主题搜索,搜索引擎将搜索限制在特定的主题或类型,从而提供更准确的结果

这些例子显示了文本分类在信息检索系统中的重要性;因此,大多数现代信息检索系统使用某种文本分类器。在本书中,我们将使用的分类任务是用于检测垃圾邮件的文本分类。

本章我们将继续介绍 Mallet,这是一个基于 Java 的包,用于统计自然语言处理、文档分类、聚类、主题建模、信息提取和其他文本的机器学习应用。然后,我们将介绍两个文本分析应用程序,即主题建模和作为文本分类的垃圾邮件检测。

安装木槌

Mallet 可以在 mallet.cs.umass.edu/download.ph… 大学的网站上下载。导航到下载部分,如下图所示,并选择最新的稳定版本( 2.0.8 ,在撰写本书时):

下载 ZIP 文件并提取内容。在解压后的目录中,您应该会找到一个名为dist的文件夹,其中有两个 JAR 文件:mallet.jarmallet-deps.jar。第一个包含所有打包的 Mallet 类,而第二个包含所有的依赖项。我们将把这两个 JARs 文件作为引用库包含在您的项目中,如下面的屏幕截图所示:

如果您正在使用 Eclipse,右键单击 Project,选择 Properties,然后选择 Java Build Path。选择“库”选项卡,然后单击“添加外部 jar”。现在,选择两个 jar 文件并确认,如下面的屏幕截图所示:

现在我们准备开始使用木槌。

使用文本数据

文本挖掘的主要挑战之一是将非结构化的书面自然语言转换成结构化的基于属性的实例。该过程包括许多步骤,如下所示:

首先,我们从互联网、现有文档或数据库中提取一些文本。在第一步结束时,文本仍然可以以 XML 格式或其他专有格式呈现。下一步是提取实际的文本并将其分割成文档的各个部分,例如标题、大标题、摘要和正文。第三步是规范化文本编码,以确保字符以相同的方式呈现;例如,以 ASCII、ISO 8859-1 和 Windows-1250 等格式编码的文档被转换为 Unicode 编码。接下来,标记化将文档拆分成特定的单词,而下一步将删除通常预测能力较低的常用单词,例如,the、a、I 和 we。

可以包括词性 ( 词性)标记和词条化步骤,通过移除词尾和修饰语,将每个单词转换为其基本形式,这被称为词条。比如跑步变成了跑,更好变成了好。一种简化的方法是词干化,它对单个单词进行操作,而不考虑该特定单词如何使用的任何上下文,因此不能根据词性来区分具有不同含义的单词,例如,axes 作为 axes 以及 axis 的复数。

最后一步将记号转换到特征空间。最常见的情况是,特征空间是一个单词袋 ( 鞠躬)表示。在这个演示中,创建了出现在数据集中的所有单词的集合。然后,每个文档以一个向量的形式呈现,该向量计算某个特定单词在文档中出现的次数。

考虑以下两个句子的例子:

  • 雅各布喜欢乒乓球。艾玛也喜欢乒乓球
  • 雅各布也喜欢篮球

这个例子中的 BoW 由{Jacob,likes,table,tennis,Emma,too,also,basketball}组成,它有八个不同的单词。现在可以使用列表的索引将这两个句子表示为向量,指示特定索引处的单词在文档中出现的次数,如下所示:

  • [1, 2, 2, 2, 1, 0, 0, 0]
  • [1, 1, 0, 0, 0, 0, 1, 1]

这样的向量最终成为进一步学习的实例。

另一个基于 BoW 模型的非常强大的演示是 word2vec 。Word2vec 于 2013 年由谷歌的托马斯·米科洛夫领导的研究团队推出。Word2vec 是一个学习单词的分布式表示的神经网络。这个演示的一个有趣的特性是单词以簇的形式出现,因此一些单词关系,例如类比,可以使用向量数学来重现。一个著名的例子说明了国王男人+女人回报王后。进一步的细节和实施可在以下链接获得:【code.google.com/archive/p/w…

导入数据

在这一章中,我们不会研究如何从网站上删除一组文档或者从数据库中提取它们。相反,我们将假设我们已经将它们收集为一组文档,并以.txt文件格式存储它们。现在让我们来看看加载它们的两个选项。第一个选项解决了每个文档存储在自己的.txt文件中的情况。第二个选项通过每行获取一个文档来解决所有文档都存储在一个文件中的情况。

从目录导入

Mallet 支持用cc.mallet.pipe.iterator.FileIterator类从目录中读取。文件迭代器由以下三个参数构成:

  • 包含文本文件的File[]目录列表
  • 指定在目录中选择哪些文件的文件过滤器
  • 应用于文件名以产生类别标签的模式

考虑结构化到文件夹中的数据,如下面的屏幕截图所示。我们将文档按文件夹(techentertainmentpoliticssportbusiness)组织成五个主题。每个文件夹都包含特定主题的文档,如下面的屏幕截图所示:

在这种情况下,我们初始化iterator如下:

FileIterator iterator = 
  new FileIterator(new File[]{new File("path-to-my-dataset")}, 
  new TxtFilter(), 
  FileIterator.LAST_DIRECTORY); 

第一个参数指定了我们的根文件夹的路径,第二个参数将迭代器限制为仅针对.txt文件,而最后一个参数要求方法使用路径中的最后一个目录名作为类标签。

从文件导入

加载文档的另一个选项是通过cc.mallet.pipe.iterator.CsvIterator.CsvIterator(Reader, Pattern, int, int, int),它假设所有的文档都在一个文件中,并在正则表达式提取的每一行返回一个实例。该类由以下组件初始化:

  • Reader:这是指定如何从文件中读取的对象
  • Pattern:这是一个正则表达式,提取三组:数据、目标标签、文档名
  • int, int, int:这些是出现在正则表达式中的数据、目标和名称组的索引

考虑以下格式的文本文档,指定文档名称、类别和内容:

AP881218 local-news A 16-year-old student at a private 
   Baptist...  
AP880224 business The Bechtel Group Inc. offered in 1985 to...  
AP881017 local-news A gunman took a 74-year-old woman hostage...  
AP900117 entertainment Cupid has a new message for lovers 
   this...  
AP880405 politics The Reagan administration is weighing w...  

要将一行解析为三组,我们可以使用以下正则表达式:

^(\\S*)[\\s,]*(\\S*)[\\s,]*(.*)$

括号()中出现了三组,其中第三组包含数据,第二组包含目标类,第一组包含文档 ID。iterator初始化如下:

CsvIterator iterator = new CsvIterator ( 
fileReader, 
Pattern.compile("^(\\S*)[\\s,]*(\\S*)[\\s,]*(.*)$"), 
  3, 2, 1)); 

这里,正则表达式提取由空格分隔的三个组,它们的顺序是3, 2, 1

现在让我们转到数据预处理管道。

预处理文本数据

一旦我们初始化了一个遍历数据的迭代器,我们就需要通过一系列的转换来传递数据,如本节开头所述。Mallet 通过管道和管道中可能包含的各种各样的步骤来支持这个过程,这些都被收集在cc.mallet.pipe包中。一些例子如下:

  • Input2CharSequence:这是一个可以从各种文本源(URL、文件或阅读器)读入CharSequence的管道
  • CharSequenceRemoveHTML:这个管道从CharSequence中移除 HTML
  • MakeAmpersandXMLFriendly:将令牌序列的令牌中的&转换为&amp
  • TokenSequenceLowercase:将数据字段中令牌序列中每个令牌的文本转换成小写
  • TokenSequence2FeatureSequence:将每个实例的数据字段中的令牌序列转换为特征序列
  • TokenSequenceNGrams:将数据字段中的令牌序列转换为 ngrams 的令牌序列,即两个或多个单词的组合

处理步骤的完整列表可在以下 Mallet 文档中找到:mallet.cs.umass.edu/api/index.html?cc/mallet/pipe/iterator/package-tree . html

现在我们准备构建一个导入数据的类。我们将通过以下步骤实现这一点:

  1. 让我们构建一个管道,其中每个处理步骤在 Mallet 中都表示为一个管道。管道可以用一系列ArrayList<Pipe>对象以串行方式连接在一起:
ArrayList<Pipe> pipeList = new ArrayList<Pipe>(); 
  1. 让我们从从 file 对象读取数据并将所有字符转换成小写开始:
pipeList.add(new Input2CharSequence("UTF-8")); 
pipeList.add( new CharSequenceLowercase() );
  1. 我们将使用正则表达式来标记原始字符串。以下模式包括 unicode 字母和数字以及下划线字符:
Pattern tokenPattern = 
Pattern.compile("[\\p{L}\\p{N}_]+"); 

pipeList.add(new CharSequence2TokenSequence(tokenPattern)); 
  1. 我们现在将使用标准的英语停用词表删除停用词,即没有预测能力的常用词。两个额外的参数指示停用字词的删除是否应该区分大小写并标记删除,而不仅仅是删除字词。我们将它们都设置为false:
pipeList.add(new TokenSequenceRemoveStopwords(new File(stopListFilePath), "utf-8", false, false, false));
  1. 我们可以不存储实际的单词,而是将它们转换成整数,在 BoW 中表示一个单词索引:
pipeList.add(new TokenSequence2FeatureSequence()); 
  1. 我们将为类标签做同样的事情;我们将使用一个整数来代替标签字符串,表示标签在单词包中的位置:
pipeList.add(new Target2Label()); 
  1. 我们还可以通过调用PrintInputAndTarget管道来打印特征和标签:
pipeList.add(new PrintInputAndTarget()); 
  1. 我们将管道列表存储在一个SerialPipes类中,该类将通过一系列管道转换实例:
SerialPipes pipeline = new SerialPipes(pipeList); 

现在让我们看看如何在文本挖掘应用程序中应用它!

BBC 新闻的主题建模

如前所述,主题建模的目标是识别文本语料库中对应于文档主题的模式。在这个例子中,我们将使用来自 BBC 新闻的数据集。该数据集是机器学习研究中的标准基准之一,可用于非商业和研究目的。

我们的目标是构建一个分类器,能够将一个主题分配给一个未分类的文档。

BBC 数据集

2006 年,格林和坎宁安收集了英国广播公司的数据集来研究一个特定的文档— 使用支持向量机的聚类挑战。该数据集由 2004 年至 2005 年 BBC 新闻网站的 2225 份文件组成,对应于从五个主题领域收集的故事:商业、娱乐、政治、体育和技术。数据集可以在以下网站看到:【mlg.ucd.ie/datasets/bb…

我们可以在 Dataset: BBC 部分下载原始文本文件。您还会注意到,该网站包含一个已经处理过的数据集,但是,对于本例,我们希望自己处理数据集。ZIP 包含五个文件夹,每个主题一个。实际的文档放在相应的主题文件夹中,如下面的屏幕截图所示:

现在,让我们构建一个主题分类器。

建模

我们将使用以下步骤开始建模阶段:

  1. 我们将从导入数据集开始,并使用以下代码行处理文本:
import cc.mallet.types.*; 
import cc.mallet.pipe.*; 
import cc.mallet.pipe.iterator.*; 
import cc.mallet.topics.*; 

import java.util.*; 
import java.util.regex.*; 
import java.io.*; 

public class TopicModeling { 

  public static void main(String[] args) throws Exception { 

String dataFolderPath = "data/bbc"; 
String stopListFilePath = "data/stoplists/en.txt"; 
  1. 然后我们将创建一个默认的pipeline对象,如前所述:
ArrayList<Pipe> pipeList = new ArrayList<Pipe>(); 
pipeList.add(new Input2CharSequence("UTF-8")); 
Pattern tokenPattern = Pattern.compile("[\\p{L}\\p{N}_]+"); 
pipeList.add(new CharSequence2TokenSequence(tokenPattern)); 
pipeList.add(new TokenSequenceLowercase()); 
pipeList.add(new TokenSequenceRemoveStopwords(new File(stopListFilePath), "utf-8", false, false, false)); 
pipeList.add(new TokenSequence2FeatureSequence()); 
pipeList.add(new Target2Label()); 
SerialPipes pipeline = new SerialPipes(pipeList); 
  1. 接下来,我们将初始化folderIterator对象:
FileIterator folderIterator = new FileIterator( 
    new File[] {new File(dataFolderPath)}, 
    new TxtFilter(), 
    FileIterator.LAST_DIRECTORY);
  1. 我们现在将使用我们想要用来处理文本的pipeline构建一个新的实例列表:
InstanceList instances = new InstanceList(pipeline);
  1. 我们处理由iterator提供的每个实例:
instances.addThruPipe(folderIterator); 
  1. 现在让我们使用实现简单线程 LDA 模型的cc.mallet.topics.ParallelTopicModel.ParallelTopicModel类创建一个包含五个主题的模型。LDA 是一种常见的主题建模方法,它使用狄利克雷分布来估计所选主题生成特定文档的概率。在这一章中,我们不会深究细节;读者可参考 D. Blei 等人(2003 年)的原始论文。

注:机器学习中还有另一种同样初始化的分类算法是指线性判别分析 ( LDA )。除了常见的缩写,它与 LDA 模型没有任何共同之处。

该类是用参数 alpha 和 beta 实例化的,可以大致解释如下:

  • 高 alpha 值意味着每个文档可能包含大部分主题的混合,而不是特定的单个主题。较低的 alpha 值对文档施加的约束较少,这意味着文档更有可能包含几个甚至一个主题的混合。
  • 高 beta 值意味着每个主题可能包含大多数单词的混合,而不是任何特定的单词;而低值意味着一个主题可能只包含几个单词的组合。

在我们的例子中,我们最初将两个参数都保持为低(alpha_t = 0.01,beta_w = 0.01),因为我们假设数据集中的主题没有混合太多,并且每个主题都有许多单词:

int numTopics = 5; 
ParallelTopicModel model =  
new ParallelTopicModel(numTopics, 0.01, 0.01); 
  1. 我们将把instances添加到模型中,由于我们使用并行实现,我们将指定并行运行的线程数量,如下所示:
model.addInstances(instances); 
model.setNumThreads(4);
  1. 我们现在将运行模型一个选定的迭代次数。每次迭代用于更好地估计内部 LDA 参数。对于测试,我们可以使用较小的迭代次数,例如 50 次;而在实际应用中,使用10002000迭代。最后,我们将调用实际构建 LDA 模型的void estimate() 方法:
model.setNumIterations(1000); 
model.estimate(); 

该模型输出以下结果:

    0 0,06654  game england year time win world 6 
    1 0,0863  year 1 company market growth economy firm 
    2 0,05981  people technology mobile mr games users music 
    3 0,05744  film year music show awards award won 
    4 0,11395  mr government people labour election party blair 

    [beta: 0,11328] 
    <1000> LL/token: -8,63377

    Total time: 45 seconds

LL/token表示模型的对数似然性,除以标记总数,表示数据给定模型的可能性。值增加意味着模型在改进。

输出还显示了描述每个主题的热门词汇。这些词非常符合最初的主题:

  • Topic 0 : game , england , year , time , win , world , 6  ⇒ sport
  • 话题一 : year1companymarketgrowtheconomyfirm财务
  • 话题二 : peopletechnologymobilemrgamesusersmusictech
  • 话题三 : filmyearmusicshowawardsawardwon娱乐
  • 话题四 : mrgovernmentpeoplelaborelectionpartyblair政治

还有一些词没有多大意义,比如mr16。我们可以把它们包含在停用词列表中。还有,有些词会出现两次,例如,awardawards。这是因为我们没有应用任何词干分析器或词尾化管道。

在下一节中,我们将看看这个模型是否有任何优点。

评估模型

由于统计主题建模具有无监督的性质,这使得模型选择困难。对于某些应用程序,可能会有一些外部任务,如信息检索或文档分类,可以对这些任务的性能进行评估。然而,一般来说,我们希望评估模型概括主题的能力,而不考虑任务。

在 2009 年,Wallach 等人介绍了一种方法,通过计算模型下被拒绝的文档的对数概率来测量模型的质量。看不见文档的可能性可用于比较模型——更高的可能性意味着更好的模型。

我们将使用以下步骤评估模型:

  1. 让我们将文档划分为训练集和测试集(即延期文档),其中 90%用于训练,10%用于测试:
// Split dataset 
InstanceList[] instanceSplit= instances.split(new Randoms(), new 
   double[] {0.9, 0.1, 0.0}); 
  1. 现在让我们仅使用我们的文档来重建我们的模型:
// Use the first 90% for training 
model.addInstances(instanceSplit[0]); 
model.setNumThreads(4); 
model.setNumIterations(50); 
model.estimate(); 
  1. 我们将初始化一个estimator对象,该对象实现 Wallach 的持有文档的对数概率,MarginalProbEstimator:
// Get estimator 
MarginalProbEstimator estimator = model.getProbEstimator(); 

Annalyn Ng 在她的博客中总结了对 LDA 的直观描述:annalyzin . WordPress . com/2015/06/21/laymans-explain-of-topic-modeling-with-LDA-2/。要更深入地了解 LDA 算法、其组件及其工作原理,请查看 David Blei 等人(2003 年)在 jmlr.csail.mit.edu/papers/v3/b… 发表的论文 LDA 原文,或者查看布朗大学的 D. Santhanam 在www . cs . Brown . edu/courses/csci 2950-p/spring 2010/lectures/2010-03-03 _ Santhanam . pdf发表的摘要。

该类实现了许多估计器,这些估计器需要对 LDA 方法如何工作有相当深入的理论知识。我们将选择从左到右的评估器,它适用于广泛的应用程序,包括文本挖掘和语音识别。从左到右赋值器被实现为double evaluateLeftToRight方法,接受以下组件:

  • Instances heldOutDocuments:测试实例。
  • int numParticles:该算法参数表示从左到右的令牌数,默认值为 10。
  • boolean useResampling:表示是否在从左到右的评估中对主题进行重采样;重采样更精确,但会导致文档长度的二次缩放。
  • PrintStream docProbabilityStream:这是一个文件或stdout,我们在其中写入每个文档的推断对数概率。
  1. 让我们运行estimator,如下所示:
double loglike = estimator.evaluateLeftToRight( 
  instanceSplit[1], 10, false, null);); 
System.out.println("Total log likelihood: "+loglike); 

在我们的特殊情况下,estimator输出下面的log likelihood,这在与使用不同参数、管道或数据构建的其他模型相比时是有意义的——对数似然性越高,模型越好:

    Total time: 3 seconds
    Topic Evaluator: 5 topics, 3 topic bits, 111 topic mask
    Total log likelihood: -360849.4240795393

现在让我们来看看如何利用这个模型。

重用模型

由于我们通常不会动态地构建模型,因此训练一次模型并重复使用它来分类新数据通常是有意义的。

请注意,如果您想要对新文档进行分类,它们需要像其他文档一样通过相同的管道——训练和分类的管道需要相同。在训练期间,管道的数据字母表随着每个训练实例而更新。如果使用相同的步骤创建新管道,则不会产生相同的管道,因为其数据字母表为空。因此,要在新数据上使用模型,我们必须保存或加载管道和模型,并使用该管道添加新实例。

保存模型

Mallet 支持基于序列化保存和恢复对象的标准方法。

我们简单地创建一个ObjectOutputStream类的新实例,并将对象写入一个文件,如下所示:

String modelPath = "myTopicModel"; 

//Save model 
ObjectOutputStream oos = new ObjectOutputStream( 
new FileOutputStream (new File(modelPath+".model"))); 
oos.writeObject(model); 
oos.close();    

//Save pipeline 
oos = new ObjectOutputStream( 
new FileOutputStream (new File(modelPath+".pipeline"))); 
oos.writeObject(pipeline); 
oos.close(); 

恢复模型

恢复通过序列化保存的模型只是一个使用ObjectInputStream类的逆向操作:

String modelPath = "myTopicModel"; 

//Load model 
ObjectInputStream ois = new ObjectInputStream( 
  new FileInputStream (new File(modelPath+".model"))); 
ParallelTopicModel model = (ParallelTopicModel) ois.readObject(); 
ois.close();    

// Load pipeline 
ois = new ObjectInputStream( 
  new FileInputStream (new File(modelPath+".pipeline"))); 
SerialPipes pipeline = (SerialPipes) ois.readObject(); 
ois.close();    

我们讨论了如何构建一个 LDA 模型来将文档自动分类到主题中。在下一个例子中,我们将研究另一个文本挖掘问题——文本分类。

检测垃圾邮件

垃圾邮件或电子垃圾邮件是指未经请求的消息,通常带有广告内容、受感染的附件、指向网络钓鱼或恶意软件站点的链接等。虽然最广泛认可的垃圾邮件形式是电子邮件垃圾邮件,但垃圾邮件滥用也出现在其他媒体中:网站评论、即时消息、互联网论坛、博客、在线广告等等。

在本章中,我们将讨论如何建立朴素贝叶斯垃圾邮件过滤,使用 BoW 表示来识别垃圾邮件。朴素贝叶斯垃圾邮件过滤是在第一个商业垃圾邮件过滤器中实现的基本技术之一;例如,Mozilla Thunderbird 邮件客户端使用这种过滤的本地实现。虽然本章中的示例将使用电子邮件垃圾邮件,但基本方法也可以应用于其他类型的基于文本的垃圾邮件。

垃圾邮件数据集

2000 年,Androutsopoulos 等人收集了第一批垃圾邮件数据集之一,用于测试垃圾邮件过滤算法。他们研究了如何使用朴素贝叶斯分类器来检测垃圾邮件,如果额外的管道(如非索引列表、词干分析器和词汇化)有助于提高性能。该数据集由吴恩达在 OpenClassroom 的机器学习课上进行了重组,可在open classroom . Stanford . edu/main folder/document page . PHP 下载?课程=机器学习&doc =练习/ex6/ex6.html

选择并下载第二个选项ex6DataEmails.zip,如下图所示:

ZIP 包含以下文件夹:

  • nonspam-trainspam-train文件夹包含您将用于培训的预处理电子邮件。他们每人有 350 封电子邮件。
  • nonspam-testspam-test文件夹构成测试集,包含 130 封垃圾邮件和 130 封非垃圾邮件。这些是你将要做预测的文件。请注意,即使单独的文件夹告诉您正确的标签,您也应该在没有这些知识的情况下对所有的测试文档进行预测。做出预测后,您可以使用正确的标注来检查您的分类是否正确。

为了利用 Mallet 的文件夹迭代器,让我们按如下方式重新组织文件夹结构。我们将创建两个文件夹,traintest,并将spam/nospam文件夹放在相应的文件夹下。初始文件夹结构如下图所示:

最终的文件夹结构如下图所示:

下一步是将电子邮件转换成特征向量。

特征生成

我们将使用以下步骤执行特征生成:

  1. 我们将创建一个默认管道,如前所述:
ArrayList<Pipe> pipeList = new ArrayList<Pipe>(); 
pipeList.add(new Input2CharSequence("UTF-8")); 
Pattern tokenPattern = Pattern.compile("[\\p{L}\\p{N}_]+"); 
pipeList.add(new CharSequence2TokenSequence(tokenPattern)); 
pipeList.add(new TokenSequenceLowercase()); 
pipeList.add(new TokenSequenceRemoveStopwords(new 
   File(stopListFilePath), "utf-8", false, false, false)); 
pipeList.add(new TokenSequence2FeatureSequence()); 
pipeList.add(new FeatureSequence2FeatureVector()); 
pipeList.add(new Target2Label()); 
SerialPipes pipeline = new SerialPipes(pipeList); 

注意,我们添加了一个额外的FeatureSequence2FeatureVector管道,将一个特征序列转换成一个特征向量。当我们在特征向量中有数据时,我们可以使用任何分类算法,就像我们在前面的章节中看到的那样。我们将继续 Mallet 中的例子来演示如何构建分类模型。

  1. 我们初始化一个文件夹迭代器,将我们的示例加载到由spamnonspam子文件夹中的电子邮件示例组成的train文件夹中,这些示例将被用作示例标签:
FileIterator folderIterator = new FileIterator( 
    new File[] {new File(dataFolderPath)}, 
    new TxtFilter(), 
    FileIterator.LAST_DIRECTORY); 
  1. 我们将用我们想要用来处理文本的pipeline对象构建一个新的实例列表:
InstanceList instances = new InstanceList(pipeline); 
  1. 我们将处理迭代器提供的每个实例:
instances.addThruPipe(folderIterator); 

我们现在已经加载了数据,并将其转换为特征向量。让我们在训练集上训练我们的模型,并在test集上预测spam/nonspam分类。

培训和测试

Mallet 在cc.mallet.classify包中实现了一组分类器,包括决策树、朴素贝叶斯、AdaBoost、bagging、boosting 等等。我们将从一个基本的分类器开始,即朴素贝叶斯分类器。分类器由ClassifierTrainer类初始化,当我们调用它的train(Instances)方法时,它返回一个分类器:

ClassifierTrainer classifierTrainer = new NaiveBayesTrainer(); 
Classifier classifier = classifierTrainer.train(instances); 

现在让我们看看这个分类器是如何工作的,并在一个单独的数据集上评估它的性能。

模型性能

要在单独的数据集上评估分类器,我们将使用以下步骤:

  1. 让我们从导入位于test文件夹中的电子邮件开始:
InstanceList testInstances = new 
   InstanceList(classifier.getInstancePipe()); 
folderIterator = new FileIterator( 
    new File[] {new File(testFolderPath)}, 
    new TxtFilter(), 
    FileIterator.LAST_DIRECTORY); 
  1. 我们将通过我们在培训期间初始化的同一管道传递数据:
testInstances.addThruPipe(folderIterator); 
  1. 为了评估分类器的性能,我们将使用cc.mallet.classify.Trial类,它由一个分类器和一组测试实例初始化:
Trial trial = new Trial(classifier, testInstances); 
  1. 初始化时立即执行评估。然后,我们可以简单地删除我们关心的措施。在我们的示例中,我们希望检查垃圾邮件分类的精确度和召回率,或 F-measure,它返回两个值的调和平均值,如下所示:
System.out.println( 
  "F1 for class 'spam': " + trial.getF1("spam")); 
System.out.println( 
  "Precision:" + trial.getPrecision(1)); 
System.out.println( 
  "Recall:" + trial.getRecall(1)); 

评估对象输出以下结果:

    F1 for class 'spam': 0.9731800766283524
    Precision: 0.9694656488549618
    Recall: 0.9769230769230769

结果表明,该模型正确发现了 97.69%的垃圾邮件(召回),当它将一封邮件标记为垃圾邮件时,它在 96.94%的情况下是正确的。换句话说,它每 100 封垃圾邮件中会漏掉大约 2 封,每 100 封有效邮件中会有 3 封被标记为垃圾邮件。所以,它并不真的完美,但它不仅仅是一个好的开始!

摘要

在这一章中,我们讨论了文本挖掘与传统的基于属性的学习有何不同,它需要大量的预处理步骤来将书面自然语言转换为特征向量。此外,我们讨论了如何通过将 Mallet 应用于两个实际问题来利用它,Mallet 是 NLP 的一个基于 Java 的库。首先,我们使用 LDA 模型对新闻语料库中的主题进行建模,以建立能够将主题分配给新文档的模型。我们还讨论了如何使用 BoW 表示构建朴素贝叶斯垃圾邮件过滤分类器。

本章总结了如何应用各种库来解决机器学习任务的技术演示。由于我们无法涵盖更多有趣的应用,也无法在许多方面给出更多细节,下一章将给出一些关于如何继续学习和深入特定主题的进一步指导。

十一、下一步是什么?

本章结束了我们回顾 Java 库中机器学习的旅程,并讨论了如何利用它们来解决现实生活中的问题。然而,这绝不应该是你旅程的终点。这一章会给你一些实用的建议,告诉你如何在现实世界中开始部署你的模型,有哪些陷阱,以及到哪里去加深你的知识。它还为您提供了进一步的指导,告诉您在哪里可以找到更多的资源、材料、场所和技术来更深入地研究机器学习。

本章将涵盖以下主题:

  • 现实生活中机器学习的重要方面
  • 标准和标记语言
  • 云中的机器学习
  • 网络资源和竞赛

现实生活中的机器学习

论文、会议报告和讲座通常不会讨论模型在生产环境中是如何实际部署和维护的。在这一节中,我们将探讨一些应该考虑的方面。

噪声数据

在实践中,由于各种原因,例如测量误差、人为错误以及在对训练样本进行分类时专家判断的误差,数据通常包含误差和缺陷。我们将所有这些称为噪声。当具有未知属性值的示例被一组对应于缺失值的概率分布的加权示例替换时,噪声也可能来自缺失值的处理。学习数据中的噪声的典型后果是新数据中的学习模型的低预测精度以及用户难以解释和理解的复杂模型。

阶级不平衡

类别不平衡是我们在第七章、欺诈和异常检测中遇到的一个问题,我们的目标是检测欺诈性保险索赔。面临的挑战是,数据集的很大一部分(通常超过 90%)描述了正常活动,只有一小部分数据集包含欺诈示例。在这种情况下,如果模型总是预测正常,那么它在 90%的情况下是正确的。这个问题在实践中非常普遍,并且可以在各种应用中观察到,包括欺诈检测、异常检测、医疗诊断、漏油检测和面部识别。

现在,知道了什么是阶级失衡问题,为什么它是一个问题,我们来看看如何处理这个问题。第一种方法是关注分类准确度之外的度量,比如召回率、精确度和 f-measure。这样的衡量标准集中在一个模型在预测少数类(回忆)时有多准确,以及虚警的比例是多少(精度)。另一种方法是基于重采样,其主要思想是以这样一种方式减少过度表示的例子的数量,即新的集合包含两类的平衡比率。

特征选择

特征选择可以说是建模中最具挑战性的部分,需要领域知识和对手头问题的深刻见解。然而,良好特性的属性如下:

  • 可重用性:特性应该可以在不同的模型、应用程序和团队中重用。
  • 可转换性:你应该能够通过一个操作转换一个特征,例如log()max(),或者通过一个自定义计算将多个特征组合在一起。
  • 可靠性:特性应该易于监控,并且应该有适当的单元测试来最小化错误或问题。
  • 可解释性:要执行前面的任何操作,您需要能够理解特性的含义并解释它们的值。

捕捉特征的能力越强,结果就越准确。

模型链接

某些模型可能会产生输出,用作另一个模型中的特征。此外,我们可以使用多个模型——集合——将任何模型转化为一个特征。这是一个获得更好结果的好方法,但是这也会导致问题。必须注意模型的输出已经准备好接受依赖关系。此外,尽量避免反馈循环,因为它们会在管道中产生依赖性和瓶颈。

评估的重要性

另一个重要的方面是模型评估。除非你将你的模型应用于新数据,并衡量一个业务目标,否则你就不是在做预测分析。评估技术,如交叉验证和分离的训练/测试集,只是简单地分割您的测试数据,这只能为您提供模型将如何执行的估计。生活中通常不会给你一个定义了所有案例的训练数据集,所以在现实世界的数据集中定义这两个数据集需要很大的创造性。

在一天结束时,我们希望提高一个业务目标,例如提高广告转化率,并在推荐的项目上获得更多的点击。为了测量改进,执行 A/B 测试,测量统计上相同的群体中的度量差异,每个群体都经历不同的算法。关于产品的决策总是由数据驱动的。

A/B 检验是一种有两个变量的随机实验的方法:A,对应原始版本,控制实验;B 对应于一个变量。该方法可用于确定变体是否优于原始版本。它可以用来测试从网站变更到销售电子邮件到搜索广告的一切。Udacity 提供免费课程,涵盖在www.udacity.com/course/ab-testing-ud 257的 A/B 测试的设计和分析。

将模型投入生产

从在实验室构建准确的模型到将其部署到产品中的过程涉及数据科学和工程的协作,如以下三个步骤所示:

  1. 数据研究和假设构建包括问题建模和执行初始评估。
  2. 解决方案构建和实现是您的模型通过将其重写为更高效、稳定和可伸缩的代码而进入产品流程的地方。
  3. 在线评估是对业务目标进行 A/B 测试,利用实时数据评估模型的最后阶段。

下图更好地说明了这一点:

模型维护

我们需要解决的另一个方面是如何维护模型。这是一种不会随时间而改变的模式吗?建模一个动态现象需要模型随时间调整它的预测吗?

该模型通常是在离线批处理定型中构建的,然后用于实时数据以提供预测,如下图所示。如果我们能够收到关于模型预测的反馈,例如,股票是否如模型预测的那样上涨,以及候选人是否对竞选活动做出了回应,那么反馈应该用于改进初始模型:

反馈对于改进初始模型非常有用,但是一定要注意你正在采样的数据。例如,如果您有一个模型来预测谁会对某个活动做出响应,那么您最初将使用一组随机联系的客户端,这些客户端具有特定的响应/未响应分布和特征属性。该模型将只关注最有可能做出回应的客户子集,您的反馈将为您返回做出回应的客户子集。通过包含这些数据,该模型在特定的子群中更加准确,但是可能会完全遗漏一些其他的群。我们称这个问题为探索与剥削。Osugi 等人(2005 年)和 Bondu 等人(2010 年)提出了解决这个问题的一些方法。

标准和标记语言

随着预测模型变得越来越普遍,共享模型和完成建模过程的需要导致开发过程和可互换格式的形式化。在这一节中,我们将回顾两个事实上的标准,一个涵盖数据科学过程,另一个指定应用程序之间共享模型的可互换格式。

CRISP-DM

跨行业数据挖掘标准流程 ( CRISP-DM )描述了数据科学家在行业中常用的数据挖掘流程。CRISP-DM 将数据挖掘科学过程分为六个主要阶段:

  • 业务理解
  • 数据理解
  • 数据准备
  • 建模
  • 评估
  • 部署

在下图中,箭头表示流程流,它可以在各个阶段之间来回移动。此外,这个过程不会随着模型部署而停止。外部箭头表示数据科学的循环性质。在此过程中吸取的经验教训可以引发新的问题,并在改进之前结果的同时重复此过程:

SEMMA 方法论

另一种方法是采样、探索、修改、建模和评估 ( 塞马)。SEMMA 描述了数据科学中的主要建模任务,同时撇开了数据理解和部署等业务方面。SEMMA 由 SAS Institute 开发,SAS Institute 是最大的统计软件供应商之一,旨在帮助其软件用户执行数据挖掘的核心任务。

预测模型标记语言

预测 模型标记语言 ( PMML )是一种基于 XML 的交换格式,允许机器学习模型在应用程序和系统之间轻松共享。支持的模型包括逻辑回归、神经网络、决策树、朴素贝叶斯、回归模型和许多其他模型。典型的 PMML 文件由以下部分组成:

  • 包含一般信息的标题
  • 数据字典,描述数据类型
  • 数据转换,指定规范化、离散化、聚合或自定义函数的步骤
  • 模型定义,包括参数
  • 列出模型使用的属性的挖掘架构
  • 允许对预测结果进行后处理的目标
  • 输出列出要输出的字段和其他后处理步骤

生成的 PMML 文件可以导入任何使用 PMML 的应用程序,如 Zementis adaptive decision 和****predictive analytics(ADAPA)和 universal PMML 插件 ( UPPI )评分引擎;Weka,内置支持回归、一般回归、神经网络、TreeModel、RuleSetModel、支持向量机 ( SVM )模型;Spark,可以导出 k-means 聚类、线性回归、岭回归、lasso 模型、二元 logistic 模型、SVM;和级联,可以将 PMML 文件转换成 Apache Hadoop 上的应用程序。

下一代 PMML 是一种被称为可移植分析格式 ( PFA )的新兴格式,它提供了一个通用界面来跨环境部署完整的工作流。

云中的机器学习

建立一个能够随着数据量的增加而扩展的完整的机器学习堆栈可能是一个挑战。最近的一波软件即服务(SaaS)基础设施即服务 ( IaaS )范式也蔓延到了机器学习领域。如今的趋势是将实际的数据预处理、建模和预测转移到云环境中,只关注建模任务。

在本节中,我们将回顾一些有前途的服务,这些服务提供算法、已经在特定领域中训练的预测模型,以及支持数据科学团队中的协作工作流的环境。

机器学习即服务

第一类是算法即服务,为您提供 API 甚至图形用户界面,将数据科学管道的预编程组件连接在一起:

  • 谷歌预测 API(Google Prediction API):它是首批通过其 web API 推出预测服务的公司之一。该服务与作为数据存储的谷歌云存储集成在一起。用户可以建立一个模型并调用 API 来获得预测。
  • BigML :它实现了一个用户友好的图形界面,支持许多存储提供商(例如,亚马逊 S3),并提供了各种各样的数据处理工具、算法和强大的可视化。
  • 微软 Azure 机器学习:这提供了一个大型的机器学习算法和数据处理功能库,以及图形用户界面,将这些组件连接到一个应用程序。此外,它还提供了完全托管的服务,您可以使用该服务将预测模型部署为现成的 web 服务。
  • 亚马逊机器学习:进入市场相当晚。它的主要优势是与其他亚马逊服务的无缝集成,而算法和用户界面的数量需要进一步改进。
  • IBM Watson Analytics(IBM Watson Analytics):它专注于为特定领域(如语音识别、机器翻译和异常检测)提供已经手工制作的模型。它通过解决特定的用例,面向广泛的行业。
  • 预测。IO :它是一个自托管的开源平台,提供从数据存储到建模再到服务预测的完整堆栈。预测。IO 可以与 Apache Spark 对话,以利用其学习算法。此外,它还附带了针对特定领域的各种模型,例如推荐系统、流失预测等。

预测 API 是一个新兴的新领域,所以这些只是一些众所周知的例子;kdnuggeswww . kdnugges . com/2015/12/machine-learning-data-science-APIs . html整理了 50 个机器学习 API 的列表。

要了解更多信息,你可以访问 PAPI,预测 API 和应用国际会议,网址为 www.papi.io 或者看看这本书:引导机器学习, L Dorard ,Createspace 独立出版社,2014 年。

网络资源和竞赛

在本节中,我们将回顾在哪里可以找到用于学习、讨论、演示或强化我们的数据科学技能的其他资源。

资料组

加州大学欧文分校是最著名的机器学习数据集仓库之一。UCI 存储库包含 300 多个数据集,涵盖各种挑战,包括扑克、电影、葡萄酒质量、活动识别、股票、出租车服务轨迹、广告等。每个数据集通常都配有一篇使用该数据集的研究论文,它可以提示您如何开始以及预测基线是什么。

可以在 archive.ics.uci.edu访问 UCI 机器学习库,如下所示:

GitHub 上还有一个保存完好的陈的收藏:/github.com/caesar0301/awesome-public-datasets

awesome 公共数据集存储库维护着 400 多个数据源的链接,这些数据源来自各个领域,包括农业、生物学、经济学、心理学、博物馆和交通运输。专门针对机器学习的数据集收集在图像处理、机器学习和数据挑战部分。

在线课程

由于在线课程的提供,学习如何成为数据科学家变得更加容易。以下是在线学习不同技能的免费资源列表:

了解机器学习更多信息的一些在线课程如下:

竞争

提高知识的最佳方式是解决实际问题,如果你想建立一个经过验证的项目组合,机器学习竞赛是一个可行的起点:

网站和博客

除了在线课程和竞赛,还有许多网站和博客发布数据科学社区的最新发展、他们解决不同问题的经验或最佳实践。一些好的起点如下:

场地和会议

以下是几个顶级学术会议的最新算法:

  • 数据库中的知识发现 ( KDD
  • 计算机视觉与模式识别 ( CVPR )
  • 神经信息处理系统年会 ( NIPS
  • 机器学习国际会议 ( ICML )
  • 数据挖掘国际会议 ( ICDM )
  • 普适计算国际联合会议 ( UbiComp )
  • 国际人工智能联合会议 ( IJCAI )

一些商务会议如下:

  • 奥莱利地层会议
  • Strata + Hadoop 世界大会
  • 预测分析世界
  • MLconf

你也可以去当地的聚会团体看看。

摘要

在本章中,我们通过讨论模型部署的某些方面来结束本书,并研究了数据科学过程和可互换预测模型格式的标准。我们还回顾了在线课程、竞赛、网络资源和会议,这些都可以帮助你掌握机器学习的艺术。

我希望这本书能启发您更深入地研究数据科学,并激励您动手尝试各种库,了解如何解决不同的问题。请记住,所有的源代码和其他资源都可以在 Packt Publishing 网站上找到:www.packtpub.com/