TowardsDataScience-博客中文翻译-2016-2018-二百九十八-

23 阅读1小时+

TowardsDataScience 博客中文翻译 2016~2018(二百九十八)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

理解卷积神经网络

原文:towardsdatascience.com/understandi…

在这篇博文中,我们将探索(或至少尝试)卷积神经网络背后的直觉,卷积神经网络是机器视觉和图像识别中最重要的深度学习技术之一。我们还将通过一个例子来使用卷积神经网络识别不同的形状。

一些背景

随着人工智能的突破继续吸引着普通公众,诸如*、【人工智能】、、【机器学习】、、【深度学习】、等术语已经被互换使用。为了更好地了解人工智能在未来几年的发展方向,理解每个术语之间的差异绝对是值得的。*

The concentric circles of Artificial Intelligence

我们可以将这三个术语视为同心圆,人工智能包含机器学习,机器学习包含深度学习。我们可以更深入地研究人工智能的历史以及这一进步是如何产生的——但是关于这个话题已经讨论了很多。

简而言之,人工智能是计算机系统的发展,它执行通常保留给人类认知的任务。例如,尽管是一个硬编码系统,但计算器是一种人工智能。用于开发这种系统的技术是这些同心圆的关键。

机器学习围绕着创建可以从大型数据集学习有用模式的系统,并因此提供有用的见解。机器学习本身分为三个主要类别——第一个是监督学习,它需要创建理解一组数据点*(上下文)和标签(结果)之间关系的系统,从而提供未标记数据点的结果。此类系统的示例包括对贷款申请是否会导致违约进行分类的系统、预测未来股票价格的系统等。或者,无监督学习正在构建能够简单地基于相似特征或特性从数据集中识别有意义模式的系统。例如,根据相似的购物行为对客户进行聚类。最后,强化学习是机器学习的一个分支,它试图将智能代理置于一个定义明确的环境中,有一组可能的动作和一个目标函数(奖励)要最大化。我们可以想到自动驾驶汽车(代理)行驶在高速公路上(环境)其唯一的目标是不发生事故(奖励)*例如。

最后,深度学习是一种在机器学习中使用的技术,它利用大量数据和多层神经网络*(可以在这里找到神经网络的优秀遍历*),以便理解数据集内的模式。最近人工智能在计算机视觉和语音识别等方面的突破几乎都回到了深度学习研究,更重要的是计算能力的商品化(一篇关于计算能力在人工智能研究中的使用的有趣博客文章可以在这里找到*)*。**

直观地说,把机器学习想象成对孩子大脑建模的尝试。儿童从他人的动作中学习(监督学习)试图辨别世界上不同物体之间的相似性,例如将形状相似的乐高积木组合在一起(无监督学习)并在没有直接输入的情况下导航困难的环境,例如攀登架(强化学习)。深度学习是机器学习中的一种技术,是人工智能最近突破的根源。

什么是卷积神经网络?

在深度学习中,出现了大量的架构和技术,可以实现许多用例,其中主要是卷积神经网络。卷积神经网络的灵感来自于对哺乳动物视觉皮层的研究,以及它们如何使用大脑中神经元的分层结构来感知世界。把这个视觉皮层模型想象成专门设计来识别不同形状的神经元群。每组神经元在看到一个物体时都会放电,并相互交流,以形成对感知物体的整体理解。

Different groups of neurons in the brain learn to recognize different groups of characteristics given an input stimulus

该系统可以被解释为神经元的分级集群,其检测输入刺激的低级特征,并在该分级结构中相互通信,以开发对象的高级检测。 将层次结构想象成如下:

  • 第一个聚类作为识别低级特征的结构(即面部轮廓)
  • 第二类是识别颜色和形状的结构(肤色或颌线)
  • 第三个是识别细节的结构(耳朵、鼻子和眼睛……)
  • 最终聚类从整体上识别整个对象(脸部和附在脸部的人)

简单来说,给定一个物体的景象,该系统有不同组的神经元,它们针对该物体的不同方面激活,并相互通信以形成大画面。

Yann LeCun 从视觉皮层的分层模型中获得灵感,开发了卷积神经网络,包含以下内容:

  • 本地连接:每一层(或集群)共享一个连接,在那里它们将学习到的特性从一个集群转移到另一个集群。
  • 分层:在不同的层(或集群)之间有一个明显的层次——这类似于说从低级特征(即耳朵、眼睛)到高级特征(面部、所讨论的人等等)的学习中有一个层次。**
  • 空间不变性:输入的移动会导致输出的同等移动——无论我们如何改变输入图像,模型都应该相应地适应和移动其输出。(人类有能力识别一个物体,即使它在各种情况下颠倒或移动)

因此,卷积神经网络架构对应于类似这样的内容:

A typical Convolutional Neural Network Architecture

  • 其中 4D 矩阵形式的输入数据包括样本数(图像数)、每个样本的高度*(每个图像的高度)、每个样本的宽度(每个图像的宽度)、通道数(这里的通道数指的是每个图像的颜色规格——彩色图像对应于红色(R)、绿色(G)和蓝色(B)像素, 因此,每幅图像都有 3 个通道——把它想象成三个相互叠加的二维矩阵,每个矩阵对应于 RGB 像素的强度,而一幅灰色图像只有 1 个通道)*。 在我们的例子中,我们将只使用灰色图像。**
  • 我们的输入数据将被连接到一个隐藏的卷积层,该层在我们的图像上应用了许多任意尺寸的任意过滤器(通常是 3×3 或 5×5)。把滤镜想象成一个尺寸为 3 x 3 (或者 5 x 5) 的小手电筒,它试图理解我们的输入图像,并绘制出特征图。从特征图中,算法可以理解我们的数据中的局部特征*(眼睛、耳朵等),而不管其位置(平移不变性)*。我们可以看到卷积运算在这里有很大的显示:**

A convolution trying to capture low level features in a picture of buildings

  • 池化是一种子采样操作,通过应用任意大小的窗口(这被称为步幅)来减少提取的特征图的维度,并根据用户的指定提取窗口的总和、最大值或平均值。在这种情况下,我们将使用最大池,对于特征图中的每个 2 x 2 窗口,我们提取最高值。这项技术帮助我们在保留信息的同时降低维度。我们可以看看下面这个操作:

  • 最后,产生 Softmax 输出的传统全连接层接收卷积和 max 池层的学习表示,并输出预测。简而言之,全连接层是神经网络中的一层,它包含当观察到某个模式时会“点亮”的节点。卷积神经网络的更详细的分解及其背后的数学可以在这里找到。

直观地说,卷积神经网络接受图像作为输入,试图破译图像的不同小特征(局部连接)而不管它们的位置(空间不变性)使用一系列数学运算(分层,汇集),以便理解正在发生的事情的全貌。这些数学运算将图像建模为一系列数字,每个数字代表像素密度*(图片上特定位置的颜色强度)。***

一个工作实例

这里可以找到一个更详细的笔记本,上面有所有的实用功能和结果。

我们的数据集是一组几何形状(三角形、圆形和矩形),可以放置在 72 x 72 的网格中的任何位置。这些图片只有一个通道,因为它们是灰度级的。我们可以在下面看到这些图像的例子:**

**

Random triangles, circles and rectangles that can have any position in the grid — utility functions on how to produce them can be found in the github link above.**

我们将使用 python 上的 Keras 包(Keras 实现卷积神经网络的详细文档可以在这里找到)来开发一个卷积神经网络,它将能够以高达 98%的准确度对每个形状进行分类。

最终,卷积神经网络代表了图像识别的一个重大突破。自动驾驶汽车、面部识别系统和医疗诊断是卷积神经网络能够实现的几个用例。然而,值得注意的是,随着新技术的出现,仍然有增长的空间。

使用 Excel 了解神经网络

原文:towardsdatascience.com/understandi…

熟悉深度学习的 TL: DR 方法

为了简化卷积神经网络的概念,我将尝试解释在开发您的深度学习模型时会发生什么。要了解更多信息,我建议在网上搜索,因为这里有大量的信息(比如这个视频)。这个解释来源于 fast.ai 库。

这张简单神经网络的图片基本上代表了这个例子中发生的事情。

Simple Neural Network

输入层

7 号图像数据来自 MNIST 数据库,我们假设您使用预训练模型进行分类。

You can see the 7 by the larger numbers in the data

隐藏层 1

隐藏图层是对输入进行转换,以便从输出图层的数据中识别更复杂的要素,从而做出更好的评估。

两个过滤器将代表不同的形状-第一个过滤器设计用于检测水平边缘,第二个过滤器检测垂直边缘。这个 3x3 滤波器被称为卷积核。针对输入中的水平边缘激活滤波器 1。Conv1 显示了对输入的 3x3 部分进行处理并乘以卷积内核后两者的激活情况。下面的一张图给了你一个更好的想法。

*虽然这在 2d 数组中表示,但它们应该作为张量堆叠在一起。其中每个矩阵代表张量中的一个切片。这些本质上都是正在发生的行操作(线性代数)。

=SUM(F11:H13*ADAD11:AFAF13)是发生的卷积。

该求和将导致输入中特定 3×3 点的激活数为 3。

This this would represent a single layer.

激活功能

接下来,我们使用非线性单元,通过使用 RELU 作为我们的激活函数来消除负面影响。接下来我们可以看到底片在下一张图中消失了。

隐藏第二层

接下来,我们做另一个卷积。Conv2 将是下一个隐藏层。这将对 Conv1 中的两个矩阵进行加权,取其和积。这里的卷积核将表示一个 2X3X3 张量。

使用 RELU 后,我们现在已经创建了我们的第二层。

Layer 1 and 2

最大池化

通过只取 Conv2 中 2x2 部分的最大值,Max pooling 将达到高度和宽度分辨率的一半。在 Maxpool 矩阵中,我们可以看到 Conv2 的 2x2 部分的最大值,即 33。池的计算速度比卷积快。同样,它给了你一些平移不变性。

输出层

接下来,我们通过在 Maxpool 中获取所有激活并给它们一个权重来构建我们的全连接层。这是通过做一个矩阵乘积来实现的。在 excel 中,我们将获取激活和权重的和积。因此,与之前解析卷积层中的每个部分不同,全连接层(密集层)将对卷积层提取并由最大池层缩减采样的特征执行分类。

这个例子只代表一个类,也就是一个数字。我们还得把剩下的数字分类。

了解 Scikit 中的数据科学分类指标-了解 Python

原文:towardsdatascience.com/understandi…

在本教程中,我们将浏览 Python 的 scikit 中的一些分类指标——从头开始学习并编写我们自己的函数,以理解其中一些函数背后的数学原理。

数据科学中预测建模的一个主要领域是分类。分类包括试图预测总体中某个特定样本来自哪个类别。例如,如果我们试图预测某个特定的患者是否会再次住院,两个可能的类别是住院(阳性)和未住院(阴性)。然后,分类模型会尝试预测每个患者是否会住院。换句话说,分类只是试图预测来自总体的特定样本应该放在哪个桶中(预测正对预测负),如下所示。

当您训练您的分类预测模型时,您会想要评估它有多好。有趣的是,有许多不同的方法来评估性能。大多数使用 Python 进行预测建模的数据科学家都使用名为 scikit-learn 的 Python 包。Scikit-learn 包含许多用于分析模型性能的内置函数。在本教程中,我们将遍历其中一些指标,并从头开始编写我们自己的函数,以理解其中一些指标背后的数学原理。如果你更喜欢阅读性能指标,请点击这里查看我在的上一篇文章。

本教程将涵盖来自sklearn.metrics的以下指标:

  • 困惑 _ 矩阵
  • 准确性 _ 得分
  • 回忆 _ 分数
  • 精度分数
  • f1 _ 分数
  • roc _ 曲线
  • roc_auc_score

G 开始了

关于样本数据集和 jupyter 笔记本,请访问我的 github 这里。假设有两个类,我们将从头开始编写自己的函数。请注意,您需要填写标记为# your code here的部分

让我们加载一个样本数据集,它具有实际标签(actual_label)和两个模型的预测概率(model_RF 和 model_LR)。这里的概率是第一类的概率。

import pandas as pd
df = pd.read_csv('data.csv')
df.head()

在大多数数据科学项目中,您将定义一个阈值来定义哪些预测概率被标记为预测正与预测负。现在让我们假设阈值是 0.5。让我们添加两个额外的列,将概率转换为预测标签。

thresh = 0.5
df['predicted_RF'] = (df.model_RF >= 0.5).astype('int')
df['predicted_LR'] = (df.model_LR >= 0.5).astype('int')
df.head()

困惑 _ 矩阵

给定一个实际标签和一个预测标签,我们可以做的第一件事是将样本分成 4 个桶:

  • 真正值-实际值= 1,预测值= 1
  • 假阳性-实际= 0,预测= 1
  • 假阴性-实际= 1,预测= 0
  • 真负值-实际值= 0,预测值= 0

这些桶可以用下面的图像表示(原始源https://en . Wikipedia . org/wiki/Precision _ and _ recall #/media/File:Precision recall . SVG),我们将在下面的许多计算中引用这个图像。

这些存储桶也可以使用如下所示的混淆矩阵来显示:

我们可以从 scikit-learn 获得混淆矩阵(作为 2x2 数组),它将实际标签和预测标签作为输入

from sklearn.metrics import confusion_matrixconfusion_matrix(df.actual_label.values, df.predicted_RF.values)

其中有 5047 个真阳性,2360 个假阳性,2832 个假阴性和 5519 个真阴性。让我们定义自己的函数来验证confusion_matrix。注意,第一个我填了,另外 3 个你需要填。

def find_TP(y_true, y_pred):
    # counts the number of true positives (y_true = 1, y_pred = 1)
    return sum((y_true == 1) & (y_pred == 1))
def find_FN(y_true, y_pred):
    # counts the number of false negatives (y_true = 1, y_pred = 0)
    return # your code here
def find_FP(y_true, y_pred):
    # counts the number of false positives (y_true = 0, y_pred = 1)
    return # your code here
def find_TN(y_true, y_pred):
    # counts the number of true negatives (y_true = 0, y_pred = 0)
    return # your code here

您可以检查您的结果是否与

print('TP:',find_TP(df.actual_label.values, df.predicted_RF.values))
print('FN:',find_FN(df.actual_label.values, df.predicted_RF.values))
print('FP:',find_FP(df.actual_label.values, df.predicted_RF.values))
print('TN:',find_TN(df.actual_label.values, df.predicted_RF.values))

让我们写一个函数来计算这四个参数,然后再写一个函数来复制confusion_matrix

import numpy as np
def find_conf_matrix_values(y_true,y_pred):
    # calculate TP, FN, FP, TN
    TP = find_TP(y_true,y_pred)
    FN = find_FN(y_true,y_pred)
    FP = find_FP(y_true,y_pred)
    TN = find_TN(y_true,y_pred)
    return TP,FN,FP,TN
def my_confusion_matrix(y_true, y_pred):
    TP,FN,FP,TN = find_conf_matrix_values(y_true,y_pred)
    return np.array([[TN,FP],[FN,TP]])

检查您的结果是否与匹配

my_confusion_matrix(df.actual_label.values, df.predicted_RF.values)

不要手动比较,让我们使用 Python 的内置assert 和 numpy 的array_equal 函数来验证我们的函数工作正常

assert  np.array_equal(my_confusion_matrix(df.actual_label.values, df.predicted_RF.values), confusion_matrix(df.actual_label.values, df.predicted_RF.values) ), 'my_confusion_matrix() is not correct for RF'assert  np.array_equal(my_confusion_matrix(df.actual_label.values, df.predicted_LR.values),confusion_matrix(df.actual_label.values, df.predicted_LR.values) ), 'my_confusion_matrix() is not correct for LR'

给定这四个桶(TP,FP,FN,TN),我们可以计算许多其他性能指标。

准确性 _ 得分

最常见的分类指标是准确度,即预测正确的样本比例,如下所示:

我们可以从 scikit-learn 获得准确度分数,它将实际标签和预测标签作为输入

from sklearn.metrics import accuracy_scoreaccuracy_score(df.actual_label.values, df.predicted_RF.values)

你的答案应该是 0。38660 . 68868686861

使用上面的公式,定义你自己的复制accuracy_score的函数。

def my_accuracy_score(y_true, y_pred):
    # calculates the fraction of samples predicted correctly
    TP,FN,FP,TN = find_conf_matrix_values(y_true,y_pred)  
    return # your code hereassert my_accuracy_score(df.actual_label.values, df.predicted_RF.values) == accuracy_score(df.actual_label.values, df.predicted_RF.values), 'my_accuracy_score failed on RF'
assert my_accuracy_score(df.actual_label.values, df.predicted_LR.values) == accuracy_score(df.actual_label.values, df.predicted_LR.values), 'my_accuracy_score failed on LR'
print('Accuracy RF: %.3f'%(my_accuracy_score(df.actual_label.values, df.predicted_RF.values)))
print('Accuracy LR: %.3f'%(my_accuracy_score(df.actual_label.values, df.predicted_LR.values)))

使用精确度作为性能度量,RF 模型比 LR 模型(0.62)更精确(0.67)。那么我们是否应该就此打住,说 RF 模型是最好的模型呢?不要!准确性并不总是评估分类模型的最佳指标。例如,假设我们试图预测一件 100 次中只有 1 次发生的事情。我们可以建立一个模型,通过说事件从未发生,获得 99%的准确性。然而,我们抓住了 0%我们关心的事件。这里的 0%度量是另一个称为召回的性能度量。

回忆 _ 分数

回忆(也称为敏感度)是您正确预测的阳性事件的比例,如下所示:

我们可以从 scikit-learn 获得准确度分数,它将实际标签和预测标签作为输入

from sklearn.metrics import recall_scorerecall_score(df.actual_label.values, df.predicted_RF.values)

使用上面的公式定义您自己的复制recall_score的函数。

def my_recall_score(y_true, y_pred):
    # calculates the fraction of positive samples predicted correctly
    TP,FN,FP,TN = find_conf_matrix_values(y_true,y_pred)  
    return # your code hereassert my_recall_score(df.actual_label.values, df.predicted_RF.values) == recall_score(df.actual_label.values, df.predicted_RF.values), 'my_accuracy_score failed on RF'
assert my_recall_score(df.actual_label.values, df.predicted_LR.values) == recall_score(df.actual_label.values, df.predicted_LR.values), 'my_accuracy_score failed on LR'
print('Recall RF: %.3f'%(my_recall_score(df.actual_label.values, df.predicted_RF.values)))
print('Recall LR: %.3f'%(my_recall_score(df.actual_label.values, df.predicted_LR.values)))

提高召回率的一种方法是通过降低预测阳性的阈值来增加您定义为预测阳性的样本数量。不幸的是,这也会增加误报的数量。另一个称为精度的性能指标考虑到了这一点。

精度分数

精度是预测的阳性事件中实际为阳性的部分,如下所示:

我们可以从 scikit-learn 获得准确度分数,它将实际标签和预测标签作为输入

from sklearn.metrics import precision_scoreprecision_score(df.actual_label.values, df.predicted_RF.values)

使用上面的公式,定义您自己的复制precision_score的函数。

def my_precision_score(y_true, y_pred):
    # calculates the fraction of predicted positives samples that are actually positive
    TP,FN,FP,TN = find_conf_matrix_values(y_true,y_pred)  
    return # your code hereassert my_precision_score(df.actual_label.values, df.predicted_RF.values) == precision_score(df.actual_label.values, df.predicted_RF.values), 'my_accuracy_score failed on RF'
assert my_precision_score(df.actual_label.values, df.predicted_LR.values) == precision_score(df.actual_label.values, df.predicted_LR.values), 'my_accuracy_score failed on LR'
print('Precision RF: %.3f'%(my_precision_score(df.actual_label.values, df.predicted_RF.values)))
print('Precision LR: %.3f'%(my_precision_score(df.actual_label.values, df.predicted_LR.values)))

在这种情况下,看起来 RF 模型在召回率和精确度上都更好。但是,如果一个模型更擅长回忆,而另一个模型更擅长精确,你会怎么做。一些数据科学家使用的一种方法叫做 F1 分数。

f1 _ 分数

f1 分数是召回率和精确度的调和平均值,分数越高,模型越好。f1 分数使用以下公式计算:

我们可以从 scikit-learn 获得 f1 分数,它将实际标签和预测标签作为输入

from sklearn.metrics import f1_scoref1_score(df.actual_label.values, df.predicted_RF.values)

使用上面的公式,定义您自己的复制f1_score的函数。

def my_f1_score(y_true, y_pred):
    # calculates the F1 score
    recall = my_recall_score(y_true,y_pred)  
    precision = my_precision_score(y_true,y_pred)  
    return # your code hereassert my_f1_score(df.actual_label.values, df.predicted_RF.values) == f1_score(df.actual_label.values, df.predicted_RF.values), 'my_accuracy_score failed on RF'
assert my_f1_score(df.actual_label.values, df.predicted_LR.values) == f1_score(df.actual_label.values, df.predicted_LR.values), 'my_accuracy_score failed on LR'
print('F1 RF: %.3f'%(my_f1_score(df.actual_label.values, df.predicted_RF.values)))
print('F1 LR: %.3f'%(my_f1_score(df.actual_label.values, df.predicted_LR.values)))

到目前为止,我们假设我们定义了 0.5 的阈值来选择哪些样本被预测为阳性。如果我们改变这个阈值,性能指标将会改变。如下图所示:

print('scores with threshold = 0.5')
print('Accuracy RF: %.3f'%(my_accuracy_score(df.actual_label.values, df.predicted_RF.values)))
print('Recall RF: %.3f'%(my_recall_score(df.actual_label.values, df.predicted_RF.values)))
print('Precision RF: %.3f'%(my_precision_score(df.actual_label.values, df.predicted_RF.values)))
print('F1 RF: %.3f'%(my_f1_score(df.actual_label.values, df.predicted_RF.values)))
print(' ')
print('scores with threshold = 0.25')
print('Accuracy RF: %.3f'%(my_accuracy_score(df.actual_label.values, (df.model_RF >= 0.25).astype('int').values)))
print('Recall RF: %.3f'%(my_recall_score(df.actual_label.values, (df.model_RF >= 0.25).astype('int').values)))
print('Precision RF: %.3f'%(my_precision_score(df.actual_label.values, (df.model_RF >= 0.25).astype('int').values)))
print('F1 RF: %.3f'%(my_f1_score(df.actual_label.values, (df.model_RF >= 0.25).astype('int').values)))

如果我们没有选择阈值,我们如何评估一个模型?一种非常常用的方法是使用受试者工作特性(ROC)曲线。

roc 曲线和 roc AUC 分数

ROC 曲线非常有助于理解真阳性率和假阳性率之间的平衡。Sci-kit learn 内置了 ROC 曲线和分析这些曲线的功能。这些函数(roc_curveroc_auc_score)的输入是实际标签和预测概率(不是预测标签)。roc_curveroc_auc_score都是复杂的函数,所以我们不会让你从头开始写这些函数。相反,我们将向您展示如何使用 sci-kit learn 的功能,并解释其中的关键点。让我们从使用roc_curve制作 ROC 图开始。

from sklearn.metrics import roc_curvefpr_RF, tpr_RF, thresholds_RF = roc_curve(df.actual_label.values, df.model_RF.values)
fpr_LR, tpr_LR, thresholds_LR = roc_curve(df.actual_label.values, df.model_LR.values)

roc_curve函数返回三个列表:

  • 阈值=按降序排列的所有唯一预测概率
  • fpr =每个阈值的假阳性率(FP / (FP + TN))
  • tpr =每个阈值的真实阳性率(TP / (TP + FN))

我们可以为每个模型绘制 ROC 曲线,如下所示。

import matplotlib.pyplot as pltplt.plot(fpr_RF, tpr_RF,'r-',label = 'RF')
plt.plot(fpr_LR,tpr_LR,'b-', label= 'LR')
plt.plot([0,1],[0,1],'k-',label='random')
plt.plot([0,0,1,1],[0,1,1,1],'g-',label='perfect')
plt.legend()
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.show()

从这个图中我们可以观察到一些东西:

  • 随机猜测标签的模型将导致黑线,并且您希望有一个在这条黑线上方有曲线的模型
  • 离黑线越远的 ROC 越好,所以 RF(红色)比 LR(蓝色)好看
  • 虽然不能直接看到,但高阈值会在左下角产生一个点,低阈值会在右上角产生一个点。这意味着,随着门槛的降低,你会以更高的 FPR 为代价获得更高的 TPR

为了分析性能,我们将使用曲线下面积指标。

from sklearn.metrics import roc_auc_scoreauc_RF = roc_auc_score(df.actual_label.values, df.model_RF.values)
auc_LR = roc_auc_score(df.actual_label.values, df.model_LR.values)print('AUC RF:%.3f'% auc_RF)
print('AUC LR:%.3f'% auc_LR)

如您所见,RF 模型的曲线下面积(AUC = 0.738)优于 LR (AUC = 0.666)。当我绘制 ROC 曲线时,我喜欢将 AUC 添加到图例中,如下所示。

import matplotlib.pyplot as plt
plt.plot(fpr_RF, tpr_RF,'r-',label = 'RF AUC: %.3f'%auc_RF)
plt.plot(fpr_LR,tpr_LR,'b-', label= 'LR AUC: %.3f'%auc_LR)
plt.plot([0,1],[0,1],'k-',label='random')
plt.plot([0,0,1,1],[0,1,1,1],'g-',label='perfect')
plt.legend()
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.show()

总体而言,在这个玩具示例中,RF 型号在所有性能指标上都胜出。

结论

在预测分析中,在两个模型之间做出决定时,选择一个性能指标非常重要。正如您在这里看到的,有许多选项可供选择(准确度、召回率、精确度、f1 分数、AUC 等)。最终,您应该使用最适合当前业务问题的性能指标。许多数据科学家更喜欢使用 AUC 来分析每个模型的性能,因为它不需要选择阈值,有助于平衡真阳性率和假阳性率。

如果您对如何改进本教程有任何建议,请留下您的评论。

理解描述性统计

原文:towardsdatascience.com/understandi…

Descriptive Statistics [Image 1] (Image courtesy: My Photoshopped Collection)

统计学是数学的一个分支,处理数据的收集、解释、组织和解释。

最初,当我们获得数据时,我们不是应用花哨的算法并做出一些预测,而是首先尝试通过应用统计技术来阅读和理解数据。通过这样做,我们能够了解数据的分布类型。

本博客旨在回答以下问题:

1.什么是描述性统计?

2.描述性统计的类型?

3.集中趋势的度量(平均值、中值、众数)

4.扩散/分散的度量(标准偏差、平均偏差、方差、百分位数、四分位数、四分位数间距)

5.什么是偏斜度?

6.什么是峰度?

7.什么是相关性?

今天,让我们一劳永逸地了解一下描述性统计。我们开始吧,

什么是描述性 S 统计学?

描述性统计包括总结和组织数据,以便于理解。与推断统计学不同,描述统计学试图描述数据,但并不试图从样本中推断出总体。这里,我们通常描述样本中的数据。这通常意味着描述统计学不同于推断统计学,它不是在概率论的基础上发展起来的。

描述性统计的类型?

描述性统计分为两类。集中趋势的度量和可变性(传播)的度量。

集中趋势的度量

集中趋势是指有一个数字可以最好地概括整个测量集合,这个数字在某种程度上是集合的“中心”。

平均值/平均值

平均值是数据的中心趋势,即一个数字,整个数据围绕这个数字展开。在某种程度上,它是一个单一的数字,可以估计整个数据集的价值。

让我们计算有 8 个整数的数据集的平均值。

Image 2

中位数

中位数是将数据分成两个相等部分的值,即当数据按升序或降序排列时,其右侧的项数与左侧的项数相同。

注意:如果你按降序排列数据,不会影响中位数,但 IQR 会是负数。我们将在本博客稍后讨论 IQR。

如果项数是奇数,中值将是中间项

如果若干项是偶数,则中值将是中间两项的平均值。

Image 3

中位数是 59,它将一组数字分成相等的两部分。由于集合中有偶数,所以答案是中间数 51 和 67 的平均值。

注:当值为等差数列时(连续项之间的差为常数。这是 2 英镑。),则中值总是等于平均值

Image 4

这 5 个数字的平均数是 6,所以是中位数。

模式

众数是在数据集中出现次数最多的词,即出现频率最高的词。

Image 5

在这个数据集中,模式是 67,因为它比其余的值多,即两倍。

但是可能有一个数据集根本没有模式,因为所有的值都出现相同的次数。如果两个值同时出现并且多于其余的值,则数据集为双峰。如果三个值同时出现并且多于其余值,则数据集为三模态,对于 n 个模式,数据集为多模态

扩散/分散的度量

分布的度量指的是数据内部的可变性。

标准偏差

标准差是每个数量和平均值之间的平均距离的度量。也就是说,数据是如何从平均值展开的。低标准偏差表示数据点倾向于接近数据集的平均值,而高标准偏差表示数据点分布在更宽的值范围内。

有些情况下,我们必须在样本或总体标准差之间做出选择。

当我们被要求找出人口的某一部分,人口的一部分的标准差时;然后我们用样本标准差。

Image 6

其中 x̅是样本的平均值。

但是当我们必须处理整个人口时,我们使用人口标准差。

Image 7

其中是总体均值。

虽然样本是总体的一部分,但它们的标准差公式应该是相同的,但事实并非如此。要了解更多信息,请参考此链接

众所周知,在描述统计学中,我们通常处理样本中的数据,而不是总体中的数据。因此,如果我们使用以前的数据集,并替换示例公式中的值,

Image 8

答案是 29.62。

平均偏差/平均绝对偏差

它是一组值中每个值之间的绝对差值的平均值,也是该组中所有值的平均值。

Mean Deviation [Image 9] (Image courtesy: My Photoshopped Collection)

所以如果我们使用之前的数据集,并替换这些值,

Image 10

答案是 23.75。

差异

方差是每个数量和平均值之间平均距离的平方。也就是说它是标准差的平方。

Image 11

答案是 877.34。

范围

范围是描述统计学中最简单的技术之一。它是最低值和最高值之差。

Image 12

范围是 99–12 = 87

百分位数

百分位数是一种表示数据集中数值位置的方法。要计算百分位数,数据集中的值应该始终按升序排列。

Image 13

中位数 59 在 8 个值中有 4 个值比它本身小。也可以这么说:在数据集中,59 是第 50 百分位,因为总项的 50%小于 59。一般情况下,如果 k第 n 个百分位,则暗示总项数的 n% 小于 k

四分位数

在统计和概率中,四分位数是将数据分成四个部分的值,前提是数据按升序排序。

Quartiles [Image 14] (Image courtesy: statsmethods.wordpress.com/2013/05/09/…)

有三个四分位值。第一个四分位值在 25 个百分点。第二个四分位数是 50 个百分点,第三个四分位数是 75 个百分点。第二个四分位数(Q2)是整个数据的中位数。第一个四分位数(Q1)是数据上半部分的中位数。第三个四分位数(Q3)是数据下半部分的中间值。

所以在这里,通过类比,

Q2 = 67:是整个数据的 50%并且是中间值。

Q1 = 41:是数据的 25 个百分点。

Q3 = 85:是日期的 75%。

四分位距(IQR) = Q3 - Q1 = 85 - 41 = 44

**注:**如果按降序排列数据,IQR 将为 -44 。大小相同,只是符号不同。如果你的数据是降序排列的,那么负 IQR 是可以的。只是我们从较大的值中否定较小的值,我们更喜欢升序排列(Q3 - Q1)。

偏斜度

偏斜度是实值随机变量关于其均值的概率分布的不对称性的度量。偏斜值可以是正的或负的,也可以是未定义的。

在完美的正态分布中,曲线两边的尾部是彼此的镜像。

当分布向左倾斜时,曲线左侧的尾部比右侧的尾部长,并且平均值小于众数。这种情况也叫负偏度。

当分布向右倾斜时,曲线右侧的尾部比左侧的尾部长,均值大于众数。这种情况也叫正偏度。

Skewness [Image 16] (Image courtesy: www.safaribooksonline.com/library/vie…)

偏度系数如何确定?

要计算样本的偏态系数,有两种方法:

1]皮尔逊第一偏度系数(众数偏度)

Image 17

2]皮尔逊第二偏度系数(中位数偏度)

Image 18

释义

  • 偏斜的方向由符号给出。零表示完全没有偏斜。
  • 负值意味着分布是负偏态的。正值意味着分布是正偏的。
  • 该系数将样本分布与正态分布进行比较。该值越大,分布与正态分布的差异就越大。

示例问题:使用皮尔逊系数#1 和#2 来找出具有以下特征的数据的偏斜度:

  • 平均值= 50。
  • 中位数= 56。
  • 模式= 60。
  • 标准偏差= 8.5。

皮尔逊第一偏度系数:-1.17。

皮尔逊第二偏度系数:-2.117。

:皮尔逊的第一个偏度系数使用了模式。因此,如果值的频率非常低,那么它不会给出集中趋势的稳定度量。例如,这两组数据中的模式都是 9:

1, 2, 3, 4, 4, 5, 6, 7, 8, 9.

在第一组数据中,该模式只出现了两次。因此,使用皮尔逊的第一偏斜系数并不是一个好主意。但是在第二盘,

1, 2, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 12, 12, 13.

模式 4 出现 8 次。因此,皮尔逊的第二偏度系数将可能给你一个合理的结果。

峭度

峰度测量的确切解释过去一直有争议,但现在已经确定了。是关于离群值的存在。峰度是数据相对于正态分布是重尾(大量异常值)还是轻尾(缺少异常值)的度量。

Kurtosis [Image 19] (Image courtesy: mvpprograms.com/help/mvpsta…)

峰度有三种类型

中层大气

中间峭度是具有与正态分布峭度相似的峭度的分布,正态分布峭度为零。

薄谷开来

分布是峰度大于中峰度分布的分布。这种分布的尾部又厚又重。如果分布曲线比中峰曲线更尖,则称之为细峰曲线。

鸭嘴兽

分布是峰度小于中峰度分布的分布。这种分布的尾部更薄。如果一条分布曲线的峰值小于一条中 kurtic 曲线,则称之为 Platykurtic 曲线。

偏度和峰度之间的主要差异在于,偏度指的是对称程度,而峰度指的是分布中异常值的存在程度。****

相互关系

相关性是一种统计技术,可以显示变量对是否相关以及相关程度如何。

Correlation [Image 20] (Image courtesy: www.statisticshowto.com/what-is-cor…)

相关的主要结果称为相关系数(或“r”)。范围从-1.0 到+1.0。r 越接近+1 或-1,这两个变量的关系就越密切。

如果 r 接近 0,说明变量之间没有关系。如果 r 为正,这意味着随着一个变量变大,另一个也变大。如果 r 为负,这意味着一个变大,另一个变小(通常称为“逆”相关)。

我希望我已经让你对什么是描述性统计有了一些了解。这是一些基本统计技术的基本运行,可以帮助您从长远角度理解数据科学。

感谢阅读。

如果你喜欢这篇文章,给这篇文章一些掌声会对你有所帮助👏。我随时欢迎你的问题和建议。你可以在脸书、推特、Linkedin 上分享这个,这样有需要的人可能会偶然发现这个。

您可以通过以下方式联系到我:

领英:www.linkedin.com/in/narkhede…

推特:【twitter.com/narkhede_sa…

github:【github.com/TheSarang

了解数据科学中的开发工具

原文:towardsdatascience.com/understandi…

一些软件开发方法已经重新定义了产品到达市场的方式和速度。DevOps 就是一个例子。

它将软件开发与信息技术相结合,注重高速度和比过去方法更短的开发生命周期。此外,那些坚持 DevOps 方法的人提供了频繁的特性和修复。

DevOps vs. DevSecOps

DevSecOps 是一种类似于 DevOps 的方法,因为它们都在一个敏捷的框架内,将项目分成更小的块。

然而,DevSecOps 将安全性融入了开发过程的每一步。它需要开发和安全部门之间的持续沟通——这两个部门通常直到后期才进行沟通。

值得注意的是,DevOps 和 DevSecOps 团队经常将自动化集成到他们的实践中。通过这种方式以及其他方式,这些方法的相似之处要多于不同之处。

DevSecOps 只是优先考虑安全性,这是一个开发人员可能了解有限的因素,除非问题导致 bug,否则不会过分关注。

一些分析师认为,比较 DevOps 和 DecSecOps 是没有用的,因为它们并不矛盾。相反,DevSecOps 代表了 DevOps 的进步。

DevOps 和 DevSecOps 的循环

在 DevOps 团队中工作的人通常首先建立工作流,然后确保存在持续的反馈循环,最后,保持实验文化。相比之下,DevSecOps 团队总是将安全性放在心上。

例如,DevSecOps 专业人员将确定最紧迫的安全挑战,并为其建立工作流。

接下来,团队针对已知的安全风险实现防御机制,并应用自动化对其进行测试。即时反馈循环向需要此类信息并能据此采取行动的人员提供有关已知漏洞和攻击的详细信息。然后,组织中的开发过程和对安全性的承诺变得更强。

真正过渡到发展合作需要一个组织的文化转变。但是,一旦发生了,就值了。强调安全性不会阻碍创新,相反,它可以通过降低在开发过程的后期修复漏洞的可能性来加速创新。

美国国土安全部对 DevSecOps 表现出兴趣

在讨论了 DevOps 和 DevSecOps 的异同之后,下一个相关主题是 DevSecOps 如何应用于数据科学。2017 年夏天,有消息称,美国国土安全部在向潜在供应商发出信息请求(RFI)文件时,发现了 DevSecOps。

RFI 是实体评估潜在供应商能力的标准化方法。具体来说,RFI 涉及可能用于美国公民和移民服务的技术,并希望了解供应商如何将 DevSecOps 原则应用于大数据、分析和机器学习。

几年前,美国公民和移民服务局采用了敏捷方法,但对其实践的调查显示,该组织在使用敏捷开发电子移民系统时忽略了关键组件,如频繁测试。尽管如此,该组织知道 DevSecOps 并希望将其应用于数据科学的事实似乎很有希望。

对于数据科学家来说,DevSecOps 并不是一个巨大的飞跃

对于为什么数据科学家应该在他们的工作中采用 DevOps 原则,已经有很多争论。例如,如果他们不断测试他们的算法的有效性,他们可能会得到更可靠的结果。此外,他们可以采用越来越一致的流程,帮助他们节省时间。

由于 DevSecOps 越来越受欢迎,对它的需求也越来越大,公司提供专门的项目,给人们所需的基础知识,让他们熟悉 DevSecOps ,并开始在他们的职业生涯中使用它。例如,参与者可能会了解安全性如何与 DevOps 实践共存,以及为什么更严格的安全性与更大的 DevOps 文化相一致。

因此,如果数据科学家已经熟悉 DevOps,或者至少出于工作目的有兴趣了解更多关于它的知识,那么了解 DevSecOps 以及它如何应用于数据科学并不困难。

大多数数据科学家已经习惯了使用自动化

如前所述,自动化是 DevSecOps 的重要组成部分。自动化也是方法学的最佳实践。此外,数据科学家通常在理解大量数据包含的内容时运行自动化脚本,或者在监督质量保证时运行

对于负责检测欺诈或恐怖主义企图的项目的数据科学家来说,自动化通常是这些安全相关目标的一部分。

众所周知,大多数数据科学家在工作中都会在一定程度上使用自动化。此外,他们应该始终关注安全性,即使他们不是 DevSecOps 团队的正式成员。这些特征使数据科学家做好了充分准备,可以正式过渡到以 DevSecOps 为中心的角色。

DevSecOps 符合当今对数据的重视

互联网用户知道,如果他们不同意放弃自己的一些细节和隐私,他们就无法使用社交媒体网站或在网上做任何事情。只要公司负责任地处理和分享数据,大多数人都可以接受。

但是,可以理解的是,与脸书等网站相关的数据泄露让用户感到不安。他们对一个网站或公司有一定程度的信任,并最终意识到这种信任可能是不值得的。

不幸的是,在其他公司之前挖掘数据并使用相关见解开发最想要的功能的竞赛可能会危及安全。

通过应用 DevSecOps 原则,数据科学家可以帮助在那些通常快速行动以跟上技术发展的公司促进隐私和安全

数据科学家需要了解 DevSecOps

对 DevSecOps 的概述将阐明为什么数据科学家理解这种方法并意识到它所包含的内容是至关重要的。

不管他们如何处理数据,DevSecOps 原则很可能适用于他们的技术。

图像由 Rawpixel

用人工智能理解事件

原文:towardsdatascience.com/understandi…

我们遇到了很多客户需求,归结起来就是使用人工智能来理解事件。有些系统需要将事件分类,有些需要监听特定的事件,有些需要预测事件。这些要求通常包括给一个事件分配一个分数,然后根据分配的分数对所有事件进行排名。

Events can be analyzed in a lot of ways, including sequence, location, numerical, categorical, image, text, and relationship data.

这些事件可以是谷歌日历事件、医疗警报、约会网站上的日期,或者在 GenRush 的情况下,事件可以根据现实世界中的事件为一家公司指明新的潜在客户。

这些事件理解问题是隐藏在“事件”一词复杂性背后的经典分类和回归需求。对于序列预测,我们使用 LSTM/GRU/RNN ,有时也使用 DNN(例如,当事件序列形成一种模式时,你可以用图表表示/查看)。但是在本文中,让我们把重点放在事件处理的非顺序和非位置部分。让我们更详细地看看如何将一个事件转化为一组人工智能可以使用的功能。

因此,我们有很多事件,我们希望人工智能在特定事件发生时通知人类用户。我们立即发现一个经典的假阳性对假阴性的难题出现了。我在医院/ 医疗通知系统中见过很多这样的例子,当时我正和 Mathieu Lemay 一起研究医疗设备。如果人工智能经常错误地通知(假阳性),那么用户将忽略这些通知。然而,如果系统错过了关键事件,那么用户会认为人工智能没有注意,这是正确的。

让我们更深入一层,讨论 AI 可以观察到的“事件”的一些特征,以便做出通知决策。模型观察物体的特征是为了理解它。我们在机器学习领域所做的工作是识别特征,并以它“喜欢”的方式将其暴露给机器学习模型。想想这种事件到特征的映射,比如一个女孩的照片?)以身高和罩杯大小为模特“特征”的杂志。就像 pinup 模型一样,我们需要一些方法来比较和对比事件。把每个事件想象成一张棒球卡,里面有一堆可处理的信息。事件可以包含以下类型的信息:序列、位置、数字、分类、图像、文本和关系。

我们决定不考虑序列数据(例如,事件之前发生了什么,事件之后发生了什么),所以让我们来谈谈这些其他事件特征。对于 GenRush ,我们从一个 Google API 获取位置数据,该 API 将地址转换成经度和纬度。我在过去的文章中提到过这类事情,也有更详细的描述。这就是通常所说的地理信息系统。位置数据可用于分类和回归,但我们不要关注这一方面,因为像序列数据一样,利用这些数据有很多注意事项。数字数据是作为一个数字有意义的东西,你可以安全地与其他数字进行比较。数字事件数据的一个例子是称为“容量”的字段。事件的容量是一个可以比较的东西。例如,门牌号不是数字。这是一个数字,但是布罗德维尔大街 888 号的房子并不比 900 号的房子小。门牌号、邮政编码和国家都是分类数据的例子。也许可以用偶数/奇数来确定房子的朝向,但作为数字是没有用的。为了避免数据集中出现大量频率为 1 的类别,这些数据需要按照频率进行组织,并进行二进制化。低频分类特征并不能真正告诉你事件,所以我们可以放心地把这些东西扔到“其他”桶里。事件中的图像数据可以是与谷歌日历事件列表相关联的一组图片。我们有一些很酷的基于 CNN 的技术来将这些数据压缩成固定大小的向量,但这超出了本文的范围。文本数据通常是事件描述的主体,但也可以包括与事件相关的元数据中的文本,例如社交媒体帖子和事件响应。我们使用单词嵌入模型来更好地理解文本的意思。关于事件的关系数据是从一个知识图中提取的,该知识图是根据爬虫可以看到的关系构建的。更具体地说,我们可以设置一个爬虫来构建所有事件的所有参与者的图,其中图中的每个节点(即顶点)是一个电子邮件地址或一个事件,而弧(即边)将一个电子邮件地址连接到一个事件。因此,参加同一事件的三个人(电子邮件)可以被拉入图中的一个关系中。这个简单的图表告诉你谁和谁一起参加活动。它有各种对事件分类有用的信息,比如有多少人参加了一个事件,一起参加事件的人群是什么,谁参加了很多事件,等等。基数是一个重要的分类特征,因为当我们想要通过将事件标记为“小”或“大”来理解它们时,知道有多少人参加真的很有帮助。我知道。似乎显而易见。但是后退一步,想想我们从一个简单的“事件”项目中提取了多少特征。

既然我们已经展示了这些特征,让我们缩小一下,看看一个事件是如何变成带有伪代码的特征向量的:

下面是 keras 中用于分类特征向量(x)和伴随的基本事实数据(y)的简单 DNN:

现在有一些棘手的问题,我们没有进入这里,像建立知识图,并行运行几个模型(CNN 与 DNN),建立一个通知框架与 AWS SES/SNS,建立二进制地图等。但是,我希望您能从本文中很好地理解如何构建一个事件分类器,首先是一个特征提取函数,然后是一个对特征向量进行分类的 DNN。

所以你有它。事件可以变成人工智能可以理解的特征。

我紧接着一些好消息写这篇文章。我们最近在深度学习人工智能方面的工作获得了两个奖项!首先是视觉深度学习推荐系统最佳论文奖。更重要的是,我们还因为一篇无人监督的深度学习论文获得了顶级论文奖;我在上一篇文章中讨论过的那个。欢乐时光!

Awards! Booyah Lemay Solutions. More on battle cries here.

We got the best paper award for this paper. It sure was an uphill climb.

我在一个不安全的 98db听 Avicii 的歌,还在爬山,并在两个明天到期的不同项目上埋头苦干。我为什么要告诉你这些?我想告诉你我的感受。就在此时此地 2017 年 10 月 8 日。在赎罪日禁食 25 小时后,上周末休息了 3 天,我回到了工作中,凌晨 2 点起床,和一个新客户计划下一件大事。为什么?我超级上进。

正如所料,我们正在发展以跟上项目的增长。新文章(和一些后期工作产品)滞后的原因是我们在过去一个月的工作节奏,加上繁重的旅行和 T2 的假期。我们有 11 个正在进行的项目。在过去的一年中,我们通常在任何时候都是 5。感谢上帝 JIRA懈怠

我们现在有 6 名工程忍者。我们有一名博士和一名博士候选人,一名硕士和硕士候选人,一名高级开发人员和一名 MBA。都是工科本科生。为什么这么多高等教育?嗯,这种机器学习的东西很难,我们需要重量级人物。我自己也做得太多了。是时候下放更多权力了。

在试图保持这篇文章的概括性的同时,我开始深入细节。太深?我愿意接受一些建设性的批评。如果你喜欢这篇关于人工智能的文章,那么请尝试一下拍手工具。轻点那个。跟着我们走。去吧。我也很高兴在评论中听到你的反馈。你怎么想呢?

编码快乐!

-丹尼尔 丹尼尔@lemay.ai ←打个招呼。 LEMAY . AI 1(855)LEMAY-AI

您可能喜欢的其他文章:

理解演变的政策梯度

原文:towardsdatascience.com/understandi…

Learning to hop backwards with EPG.

我在发射台工作。AI 。我们有两周一次的内部阅读小组,主要关注机器学习领域的最新研究论文。最近,我们研究了一种新的深度强化学习(RL)算法,称为进化策略梯度 (EPG)。

这东西很酷!

论文来源于 OpenAI 。他们作出了值得称赞的努力,使他们的工作为公众所了解;他们的博客文章提供了比研究论文更“柔和”的阅读,OpenAI 也发布了与每篇论文相关的代码(这里是 EPG 代码库博客文章)。

尽管他们尽了最大努力直观地解释这项工作,但我发现他们关于 EPG 的文章有点晦涩。我很想了解它,但我找不到任何关于它的有帮助的第三方解释。这就是为什么我决定钻研报纸,写下这篇文章:提供一个直观的解释,说明 EPG 正在发生什么。

政策梯度

要理解 EPG,首先必须理解政策梯度。我对 PG 的第一个“啊哈”时刻来自于阅读 Andrej Karpathy 的 Pong from Pixels 帖子。

如果你理解反向传播是一种在监督环境下训练神经网络的方法,PG 的基础是同一概念的简单扩展。

在 RL 问题中,策略是将输入状态映射到输出动作的任何函数。PG 的想法是训练神经网络使用反向传播来学习一个好的策略函数。实现这一点的一个聪明的方法是以受监督的方式使用选定的操作作为目标值。

A diagram stolen from Andrej Karpathy’s PG blog post. The green values represent the selected action, and loss is computed between the policy output and these values.

举例来说,假设我们的网络需要在三个动作中选择一个;它接受一些状态输入并产生一些 softmax 输出,如下所示:

Some example softmax output from a neural network choosing one of three actions.

我们可以简单地通过获取这个输出的 argmax 来选择一个动作。

A one-hot encoded representation of the selected action from the above output. (In practice, actions are usually sampled from the distribution provided by the softmax output.)

然后,我们可以使用简单的损失函数(如二进制交叉熵)来计算我们的神经网络相对于这个选定的动作向量的“损失”。

Computing binary cross-entropy loss for this example, taken from this post by Manish Chablani.

如果我们使用标准的 SGD,我们可以更新我们的神经网络,在给定相同输入状态的情况下,给出更接近于所选动作的输出。这就像说“表现得更像那样!”类似地,我们可以朝相反的方向迈出一步(类似于“梯度上升”,只是上坡而不是下坡),这就像说“不要那样做!”

这就是 PG 背后的基本思想,而且出奇的简单。所有基于 PG 的 RL 算法背后的艰难工作是弄清楚如何使用 PG:什么时候我们希望鼓励我们的策略重复一个动作?我们什么时候要劝阻?由于奖励很少,这个问题并不简单。

设计损失函数

其他 PG 方法通常通过使用预期回报函数并将预期回报与观察到的回报进行比较而成功,观察到的回报使用一些折扣因子“分布”在许多时间步骤上。这本质上是一种处理稀疏回报问题的启发式方法。

这里的推理是,导致一些观察到的奖励的决策可以被认为是“好的”,因此是鼓励的。当由于行动而观察到的回报比预期的要好时,我们应该鼓励我们的政策朝着那个方向发展。折扣奖励概念是将这种推理应用于 RL 算法的一种方式。

控制策略参数步长变得非常重要。以一种有原则的方式做到这一点是近似策略优化 (PPO)方法背后的新颖之处。这里,基于更新前后策略参数之间的 KL 偏差的惩罚被用于控制步长。

所有这些工作都可以被看作是设计一个 PG 损失函数,这个函数依赖于观察到的回报——一个优化政策的能力以最大化回报的合理要求。

为什么不学习一个损失函数?

RL 硬。PPO 在各种任务中表现相对较好。

这很好,但 EPG 的基本想法是:让我们使用机器学习来学习损失函数,而不是自己设计一个。也许这种方法可以给我们一个更通用的 RL 算法。

退一步,思考整个问题。我们需要哪些信息来学习一个好的政策?

首先,给定一个状态的“正确选择”不仅取决于当前状态,还取决于状态的最近历史。这也取决于我们最近的决策历史。我们能为 PG 设计一个考虑所有这些信息的损失函数吗?

对 EPG 来说,这就是他们正在试图做的。最终的损失函数可能如下所示:

EPG loss function architecture… quite a lot going on. The grey stuff is the loss function.

这里有很多事情要做,但主要的想法是,可以建立一个神经网络,它可以获取您想要的所有信息(包括您的策略网络输出和您选择的操作),并输出一个值(图像顶部的绿色立方体)。)我们可以将该值称为损失,并训练我们的策略网络使其最小化。

这种特殊的架构使用跨越时间的卷积。这个时间维度让我们称这些层为“时间卷积”,但它们的工作方式就像我们所了解和喜爱的卷积层一样。

最难的部分

这是一个伟大的想法,但有一个问题:我们如何为我们的损失函数网络学习良好的权重?

做这件事没有直接的方法。我们不能在这里捏造政策梯度之类的东西。

这就是进化策略派上用场的地方。

进化策略

EPG 的“进化”部分来自于使用 es 来学习损失函数网络的良好参数。

这种特殊的优化算法在这里非常有用,因为它给了我们一种不用任何梯度概念就能更新权重的方法。ES 的基本思想是这样的:通过向每个参数添加随机噪声来更新权重。如果事情变得更好,坚持这些改变。如果事情变得更糟,那就是朝着错误的方向迈出了一步(也是有用的信息。)

因此,我们创造了一个新的问题(学习一些疯狂复杂但潜在有用的损失函数的参数)和一个潜在的解决方案(应用专家系统)。这篇论文的大部分都在描述这些人是如何着手做这件事的。

开始工作

ES 包含了大量的尝试和错误。为了在合理的时间内收敛,我们需要并行化学习过程。

我们可以通过同时进行许多试验来做到这一点。这些试验中的每一个都是由一个工人(所谓的论文)或一个线程执行的。

一系列任务

A visualization of one family of tasks. The family is “learn to move the ant to a target location.” A task in this family is “learn to move the ant to location [10, 5]”.

EPG 试图解决一系列的任务。这是通过从家庭中抽取个体任务来实现的。如上图所示,任务家族的一个例子是“引导蚂蚁到达指定的目标位置”这个家族的任务是“引导蚂蚁到坐标[10,5]。”

每个工人得到从问题任务族中抽取的任务。这意味着在我们的例子中,一个工蚁会花时间学习如何让蚂蚁到达目标位置[10,5],而另一个工蚁会花时间让蚂蚁到达目标位置[5,10]。每个工人都将经历多次重复相同的任务,通过最小化新的损失函数来学习策略。

经过这么多时间步骤,我们可以看看每个工人通过这个过程积累的总回报。

尝试许多损失函数

每个工人也得到一些我们目前正在研究的损失函数的变体。我说的变体指的是“我们学习到的损失函数的最新、最好的版本——加上一些随机噪声。”

所以,每个工人都有任务和损失函数。我们可以在许多时间步之后挑选出总报酬最高的员工,并假设其损失函数的变体比其他一些更好,然后朝着用于创建该变体的随机生成噪声的方向迈出一步。

这种方法的一个问题是,一个工人可能比另一个工人有更容易的任务。例如,一个工人的任务可能是将蚂蚁引导到坐标[1,1],而另一个工人必须一直到达10,15。第一个工人的样本任务更容易,所以我们不能完全确信分配给它的策略是我们观察到的更高回报的原因。

为了解决这个问题,作者给许多工人分配了相同的损失函数变量;每个工人都有不同的任务;他们使用相同的损失函数变量来计算所有工人的平均报酬。

我们在每次试验结束时得到的结果是一组候选损失函数,以及关于它们在一系列任务中的相对表现的一些想法。我们可以使用该信息来更新损失函数参数。

奖励在哪里?

关于 EPG 的一个有趣的事情是,在一些实验中,该算法能够在不直接观察任何回报的情况下学习好的政策。关于什么构成“好”行为的所有信息都编码在损失函数中,损失函数不需要(但可以)包括观察到的奖励。

有希望的结果?

A pretrained EPG does well in “out-of-distribution” tasks at test-time; good test-time validation in RL problems is an important outstanding problem that EPG may solve.

《EPG》的作者对他们的结果持乐观态度,这种方法当然很有趣。中的工作包含了许多来自最近深度 RL 研究的有趣想法。

也许最重要的结果是 EPG 训练的损失函数在“非分布”任务中表现良好的能力。在我们之前看到的蚂蚁例子中,只使用位于蚂蚁初始位置右侧的目标来训练 EPG 损失函数。同一个损失函数能够训练一个策略,在测试时间引导蚂蚁到达蚂蚁初始位置左侧的目标。****

然而,该算法面临一些实际挑战,大多与需要繁重的计算资源(许多 CPU 核心)和相对较差的数据效率(需要许多许多尝试来学习一个好的损失函数)有关。)损失函数更新必须顺序执行,这就限制了该方法现有的并行处理量。

依我拙见,这篇论文之所以重要,不是因为它的直接结果,而是因为它介绍了一种应用 PG 方法的完全不同的方法。这只是如何着手学习损失函数的一个例子,但是可能有完全不同的方法来做同样的事情。也许学习 PG 损失函数的不同技术比这好得多。但是,据我所知,这是第一次尝试直接学习 PG 损失函数;避开损失函数设计问题可能为 PG 方法开辟一条有前途的新途径。

连续数字数据

原文:towardsdatascience.com/understandi…

了解特征工程(第一部分)

处理连续数值数据的策略

Source: pixabay.com

介绍

“有钱能使鬼推磨”是你无论选择同意还是不同意都无法忽视的东西。在今天的数字革命时代,更贴切的说法应该是“数据让世界运转”。事实上,数据已经成为企业、公司和组织的一级资产,无论其规模大小。任何智能系统,无论有多复杂,都需要由数据驱动。在任何智能系统的核心,我们都有一个或多个基于机器学习、深度学习或统计方法的算法,这些算法消耗这些数据来收集知识,并在一段时间内提供智能见解。算法本身相当幼稚,无法在原始数据上开箱即用。因此,从原始数据中设计有意义的特征是最重要的,这些特征可以被这些算法理解和消费。

机器学习管道的温和更新

任何智能系统基本上都由一个端到端的管道组成,从接收原始数据开始,利用数据处理技术从这些数据中获取、处理和设计有意义的特征和属性。然后,我们通常利用统计模型或机器学习模型等技术对这些功能进行建模,然后根据手头要解决的问题,在必要时部署该模型以供将来使用。基于 CRISP-DM 行业标准流程模型的典型标准机器学习流水线如下图所示。

A standard machine learning pipeline (source: Practical Machine Learning with Python, Apress/Springer)

接受原始数据并直接在这些数据上构建模型是有勇无谋的,因为我们不会获得预期的结果或性能,而且算法也不够智能,无法从原始数据中自动提取有意义的特征(现在有一些自动化的特征提取技术,在某种程度上可以通过深度学习方法来实现,但稍后会有更多内容!).

如上图所示,我们的主要关注领域属于数据准备方面,在经过必要的争论和预处理后,我们使用各种方法从原始数据中提取有意义的属性或特征。

动机

特征工程是构建任何智能系统的基本部分。即使你有很多更新的方法,如深度学习和元启发式方法,它们有助于自动机器学习,但每个问题都是特定领域的,更好的功能(适合该问题)通常是系统性能的决定因素。特征工程是一门艺术,也是一门科学,这就是为什么数据科学家经常在建模前的数据准备阶段花费 70%的时间。让我们来看看数据科学领域几位知名人士对特性工程的一些引用。

“想出新功能既困难又耗时,需要专业知识。‘应用机器学习’基本上是特征工程。”

——吴恩达教授。

这基本上强化了我们之前提到的数据科学家花费近 80%的时间在工程特性上,这是一个困难且耗时的过程,需要领域知识和数学计算。

“特征工程是将原始数据转换为特征的过程,这些特征能够更好地代表潜在问题预测模型,从而提高模型对不可见数据的准确性

杰森·布朗利博士

这给了我们一个关于特征工程的想法,即把数据转换成特征作为机器学习模型的输入的过程,这样高质量的特征有助于提高整体模型性能。特性也非常依赖于潜在的问题。因此,即使机器学习任务在不同的场景中可能是相同的,例如将电子邮件分类为垃圾邮件和非垃圾邮件,或者将手写数字分类,但在每个场景中提取的特征将彼此非常不同。

来自华盛顿大学的 Pedro Domingos 教授在他题为的论文中告诉了我们以下内容。

“最终,一些机器学习项目会成功,一些会失败。有什么区别?最重要的因素无疑是所使用的功能。”

佩德罗·多明戈斯教授

最后一句引语是著名的 Kaggler,Xavier Conort 说的,这句话应该会激发你对特征工程的兴趣。你们大多数人都已经知道,现实世界中棘手的机器学习问题经常会定期发布在 Kaggle 上,而 ka ggle 通常对所有人开放。

“我们使用的算法对 Kagglers 来说非常标准。…我们将大部分精力花在了功能工程上。…我们还非常小心地放弃了可能会让我们面临过度适应模型的风险的功能。”

—泽维尔·康纳特

了解功能

一个 特征 通常是在 原始数据 之上的特定表示,原始数据是一个单独的、可测量的属性,通常由数据集中的一列来描述。考虑一个普通的二维数据集,每个观察由一个描述,每个特征由一个描述,这将具有一个观察的特定值。

A generic dataset snapshot

因此,就像上图中的例子一样,每行通常表示一个特征向量,所有观测值的整个特征集形成一个二维特征矩阵,也称为特征集。这类似于表示二维数据的数据框或电子表格。通常,机器学习算法处理这些数字矩阵或张量,因此大多数特征工程技术处理将原始数据转换成这些算法容易理解的一些数字表示。

基于数据集,要素可以分为两种主要类型。固有的 原始特征 直接从数据集中获得,无需额外的数据处理或工程。 衍生特征 通常是从特征工程中获得的,我们从现有的数据属性中提取特征。一个简单的例子是从包含“出生日期”的雇员数据集中创建一个新特征“年龄”,只需从当前日期中减去他们的出生日期。**

数据有多种类型和格式,包括结构化和非结构化数据。在本文中,我们将讨论处理结构化连续数字数据的各种特征工程策略。所有这些例子都是我最近的一本书 【用 Python 进行实用机器学习】 的一部分,你可以在 GitHub 上访问本文中使用的相关数据集和代码。我还要感谢加布里埃尔·莫雷拉,他给了我一些关于特征工程技术的很好的建议。

数字数据的特征工程

数字数据通常以描述观察、记录或测量的标量值的形式表示数据。这里,数值数据是指 连续数据 ,而不是通常表示为分类数据的离散数据。数字数据也可以表示为值的向量,其中向量中的每个值或实体可以表示一个特定的特征。对于连续数值数据,整数和浮点数是最常见和最广泛使用的数值数据类型。即使数字数据可以直接输入到机器学习模型中,在建立模型之前,您仍然需要设计与场景、问题和领域相关的特征。因此,对特征工程的需求仍然存在。让我们利用 python,看看对数字数据进行要素工程的一些策略。我们首先加载以下必要的依赖项(通常在 Jupyter 笔记本中)。

**import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as spstats%matplotlib inline**

原始度量

正如我们前面提到的,原始数字数据通常可以根据上下文和数据格式直接输入到机器学习模型中。原始测量通常直接使用数字变量作为特征来表示,无需任何形式的转换或工程。通常,这些特征可以指示值或计数。让我们加载我们的一个数据集, 神奇宝贝数据集 也可以在 Kaggle 上找到。

**poke_df = pd.read_csv('datasets/Pokemon.csv', encoding='utf-8') poke_df.head()**

Snapshot of our Pokemon dataset

神奇宝贝是一个巨大的媒体专营权,围绕着虚拟人物称为神奇宝贝,代表口袋妖怪。简而言之,你可以把它们想象成拥有超能力的虚构动物!该数据集由这些字符组成,每个字符有不同的统计数据。

数值

如果仔细观察上图中的数据框快照,您会发现有几个属性表示可以直接使用的数字原始值。下面的代码片段重点描述了其中的一些特性。

**poke_df[['HP', 'Attack', 'Defense']].head()**

Features with (continuous) numeric data

因此,您可以直接将这些属性用作上述数据框中描述的要素。这些包括每个神奇宝贝的生命值,攻击和防御统计。事实上,我们也可以计算这些领域的一些基本统计措施。

**poke_df[['HP', 'Attack', 'Defense']].describe()**

Basic descriptive statistics on numeric features

有了这个,你就可以很好地了解计数、平均值、标准差和四分位数等统计指标。

计数

另一种形式的原始测量包括表示特定属性的频率、计数或出现的特征。让我们来看一个来自 百万首歌曲数据集 的数据样本,它描述了不同用户听过的歌曲的数量或频率。

**popsong_df = pd.read_csv('datasets/song_views.csv', 
                          encoding='utf-8')
popsong_df.head(10)**

Song listen counts as a numeric feature

从上面的快照可以明显看出,listen_count字段可以直接用作基于频率\计数的数字特征。

二值化

通常,原始频率或计数可能与基于正在解决的问题建立模型无关。例如,如果我正在为歌曲推荐构建一个推荐系统,我只想知道一个人是否对某首特定的歌曲感兴趣或听过。这不需要知道一首歌被听了多少次,因为我更关心他/她听过的各种歌曲。在这种情况下,与基于计数的特征相反,二进制特征是优选的。我们可以如下二进制化我们的listen_count字段。

**watched = np.array(popsong_df['listen_count']) 
watched[watched >= 1] = 1
popsong_df['watched'] = watched**

你也可以使用scikit-learn's模块中的Binarizer类代替numpy数组来执行同样的任务。

**from sklearn.preprocessing import Binarizerbn = Binarizer(threshold=0.9)
pd_watched = bn.transform([popsong_df['listen_count']])[0]
popsong_df['pd_watched'] = pd_watched
popsong_df.head(11)**

Binarizing song counts

从上面的快照中可以清楚地看到,这两种方法产生了相同的结果。因此,我们得到一个二进制化的特征,该特征指示该歌曲是否被每个用户收听过,该特征然后可以被进一步用于相关的模型中。

舍入

通常,当处理像比例或百分比这样的连续数字属性时,我们可能不需要精度很高的原始值。因此,将这些高精度百分比四舍五入成数字整数通常是有意义的。然后,这些整数可以直接用作原始值,甚至用作分类(基于离散类)特征。让我们尝试将这个概念应用到一个虚拟数据集中,描述商店商品及其受欢迎程度。

**items_popularity = pd.read_csv('datasets/item_popularity.csv',  
                               encoding='utf-8')items_popularity['popularity_scale_10'] = np.array(
                   np.round((items_popularity['pop_percent'] * 10)),  
                   dtype='int')
items_popularity['popularity_scale_100'] = np.array(
                  np.round((items_popularity['pop_percent'] * 100)),    
                  dtype='int')
items_popularity**

Rounding popularity to different scales

根据上面的输出,你可以猜到我们尝试了两种形式的舍入。这些特征以1–101–100两种尺度描述了物品的流行程度。根据场景和问题,您可以将这些值用作数字特征或分类特征。

相互作用

监督机器学习模型通常试图将输出响应(离散类或连续值)建模为输入特征变量的函数。例如,一个简单的线性回归方程可以描述为

其中输入特征由变量描述

具有由表示的权重或系数

而目标分别是预测 y 的反应*.*****

在这种情况下,这个简单的线性模型描述了输出和输入之间的关系,完全基于单独的输入特征。

然而,通常在一些真实的场景中,尝试捕获这些特征变量之间的交互作为输入特征集的一部分是有意义的。具有相互作用特征的上述线性回归公式的扩展的简单描述是

其中由表示的特征

表示交互特征。现在让我们尝试在我们的神奇宝贝数据集上设计一些交互功能。

**atk_def = poke_df[['Attack', 'Defense']]
atk_def.head()**

从输出数据帧中,我们可以看到我们有两个数值(连续)特征,AttackDefence。我们现在将通过利用scikit-learn来构建二级特性。

**from sklearn.preprocessing import PolynomialFeaturespf = PolynomialFeatures(degree=2, interaction_only=False,  
                        include_bias=False)
res = pf.fit_transform(atk_def)
res **Output
------**array([[    49.,     49.,   2401.,   2401.,   2401.],
       [    62.,     63.,   3844.,   3906.,   3969.],
       [    82.,     83.,   6724.,   6806.,   6889.],
       ..., 
       [   110.,     60.,  12100.,   6600.,   3600.],
       [   160.,     60.,  25600.,   9600.,   3600.],
       [   110.,    120.,  12100.,  13200.,  14400.]])**

上面的特征矩阵描述了总共五个特征,包括新的交互特征。我们可以在上面的矩阵中看到每个特征的程度如下。

**pd.DataFrame(pf.powers_, columns=['Attack_degree',                                    'Defense_degree'])**

查看这个输出,我们现在知道每个特性实际上代表了这里描述的度数。有了这些知识,我们现在可以为每个特性指定一个名称,如下所示。这只是为了便于理解,您应该用更好、更容易访问和简单的名称来命名您的功能。

**intr_features = pd.DataFrame(res, columns=['Attack', 'Defense',                                             'Attack^2',                                            'Attack x Defense',                                             'Defense^2'])
intr_features.head(5)**

Numeric features with their interactions

因此,上述数据框代表了我们的原始特征及其交互特征。

扔掉

使用原始连续数字要素的问题在于,这些要素中的值分布通常会有偏差。这意味着有些值会经常出现,而有些值会很少出现。除此之外,还有另一个问题是这些特征中的任何一个的值的变化范围。例如,特定音乐视频的浏览量可能异常之大( Despacito 我们正看着你呢!)有些可能真的很小。直接使用这些功能会导致许多问题,并对模型产生负面影响。因此,有策略来处理这一点,其中包括宁滨和转换。

宁滨,也称为量化,用于将连续的数字特征转换为离散的数字特征(类别)。这些离散值或数字可以被认为是类别或箱,原始的连续数值被装入或分组到这些类别或箱中。每个箱代表一个特定的强度等级,因此一个特定范围的连续数值落入其中。宁滨数据的具体策略包括固定宽度和自适应宁滨。让我们使用从 2016 FreeCodeCamp 开发者\编码者调查 中提取的数据集中的一个子集,该调查讨论了与编码者和软件开发者有关的各种属性。

**fcc_survey_df = pd.read_csv('datasets/fcc_2016_coder_survey_subset.csv', 
encoding='utf-8')fcc_survey_df[['ID.x', 'EmploymentField', 'Age', 'Income']].head()**

Sample attributes from the FCC coder survey dataset

ID.x变量基本上是每个参加调查的编码人员/开发人员的唯一标识符,其他字段是不言自明的。

固定宽度宁滨

顾名思义,在固定宽度宁滨中,我们为每个条柱提供特定的固定宽度,通常由分析数据的用户预先定义。每个 bin 都有一个预先确定的值范围,应该根据一些领域知识、规则或约束条件将这些值分配给该 bin。基于舍入的宁滨是其中一种方法,您可以使用我们之前讨论过的舍入运算来对原始值进行装箱。

现在让我们考虑编码器调查数据集中的Age特征,并看看它的分布。

**fig, ax = plt.subplots()
fcc_survey_df['Age'].hist(color='#A9C5D3', edgecolor='black',  
                          grid=False)
ax.set_title('Developer Age Histogram', fontsize=12)
ax.set_xlabel('Age', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)**

Histogram depicting developer age distribution

上面描述开发人员年龄的直方图如预期的那样稍微偏右(年龄较小的开发人员)。我们现在将根据以下方案将这些原始年龄值分配到特定的容器中

**Age Range: Bin
---------------
 0 -  9  : 0
10 - 19  : 1
20 - 29  : 2
30 - 39  : 3
40 - 49  : 4
50 - 59  : 5
60 - 69  : 6
  ... and so on**

使用我们在前面的舍入部分学到的知识,我们可以很容易地做到这一点,我们通过将底值除以 10 来舍入这些原始年龄值。

**fcc_survey_df['Age_bin_round'] = np.array(np.floor(
                              np.array(fcc_survey_df['Age']) / 10.))fcc_survey_df[['ID.x', 'Age', 'Age_bin_round']].iloc[1071:1076]**

Binning by rounding

您可以看到,每个年龄的对应箱都是基于舍入进行分配的。但是如果我们需要更多的灵活性呢?如果我们想根据自己的规则\逻辑来决定和固定框的宽度,该怎么办?基于定制范围的宁滨将帮助我们实现这一目标。让我们使用下面的方案为宁滨开发者年龄定义一些定制的年龄范围。

**Age Range : Bin
---------------
 0 -  15  : 1
16 -  30  : 2
31 -  45  : 3
46 -  60  : 4
61 -  75  : 5
75 - 100  : 6**

基于这个定制的宁滨方案,我们现在将为每个开发者年龄值标记容器,并且我们将存储容器范围以及相应的标签。

**bin_ranges = [0, 15, 30, 45, 60, 75, 100]
bin_names = [1, 2, 3, 4, 5, 6]fcc_survey_df['Age_bin_custom_range'] = pd.cut(
                                           np.array(
                                              fcc_survey_df['Age']), 
                                              bins=bin_ranges)
fcc_survey_df['Age_bin_custom_label'] = pd.cut(
                                           np.array(
                                              fcc_survey_df['Age']), 
                                              bins=bin_ranges,            
                                              labels=bin_names)
# view the binned features 
fcc_survey_df[['ID.x', 'Age', 'Age_bin_round', 
               'Age_bin_custom_range',   
               'Age_bin_custom_label']].iloc[10a71:1076]**

Custom binning scheme for developer ages

自适应宁滨

使用固定宽度宁滨的缺点是,由于我们手动决定面元范围,我们可能最终得到不规则的面元,这些面元根据落入每个面元的数据点或值的数量而不一致。有些箱子可能人口稠密,有些可能人口稀少,甚至是空的!在这些让数据自己说话的场景中,自适应宁滨是一种更安全的策略!没错,我们使用数据分布本身来决定我们的 bin 范围。

基于分位数的宁滨是用于自适应宁滨的一个好策略。分位数是特定的值或分界点,有助于将特定数值字段的连续值分布划分为离散的连续箱或区间。因此, q 分位数有助于将一个数值属性划分为 q 个相等的分区。分位数的常见例子包括:被称为中值2 分位数,它将数据分布分成两个相等的二进制;被称为四分位数4 分位数,它将数据分成 4 个相等的二进制;以及被称为十分位数10 分位数,它创建了 10 个相等宽度的二进制。现在让我们看看 developer Income字段的数据分布。

**fig, ax = plt.subplots()
fcc_survey_df['Income'].hist(bins=30, color='#A9C5D3', 
                             edgecolor='black', grid=False)
ax.set_title('Developer Income Histogram', fontsize=12)
ax.set_xlabel('Developer Income', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)**

Histogram depicting developer income distribution

上面的分布描述了收入的一个右偏,较少的开发者赚更多的钱,反之亦然。让我们采用基于四分位数的自适应宁滨方案。我们可以如下容易地得到四分位数。

**quantile_list = [0, .25, .5, .75, 1.]
quantiles = fcc_survey_df['Income'].quantile(quantile_list)
quantiles **Output
------** 0.00      6000.0
0.25     20000.0
0.50     37000.0
0.75     60000.0
1.00    200000.0
Name: Income, dtype: float64**

现在让我们在原始分布直方图中可视化这些分位数!

**fig, ax = plt.subplots()
fcc_survey_df['Income'].hist(bins=30, color='#A9C5D3', 
                             edgecolor='black', grid=False)for quantile in quantiles:
    qvl = plt.axvline(quantile, color='r')
ax.legend([qvl], ['Quantiles'], fontsize=10)ax.set_title('Developer Income Histogram with Quantiles', 
             fontsize=12)
ax.set_xlabel('Developer Income', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)**

Histogram depicting developer income distribution with quartile values

上面分布中的红线描绘了四分位值和我们的潜在仓。现在,让我们利用这一知识来构建基于四分位数的宁滨方案。

**quantile_labels = ['0-25Q', '25-50Q', '50-75Q', '75-100Q']
fcc_survey_df['Income_quantile_range'] = pd.qcut(
                                            fcc_survey_df['Income'], 
                                            q=quantile_list)
fcc_survey_df['Income_quantile_label'] = pd.qcut(
                                            fcc_survey_df['Income'], 
                                            q=quantile_list,       
                                            labels=quantile_labels)

fcc_survey_df[['ID.x', 'Age', 'Income', 'Income_quantile_range',                'Income_quantile_label']].iloc[4:9]**

Quantile based bin ranges and labels for developer incomes

这应该让你对基于分位数的自适应宁滨的工作原理有一个很好的了解。这里需要记住的重要一点是,宁滨的结果会导致离散值分类特征,在将分类数据用于任何模型之前,您可能需要对分类数据进行额外的特征工程步骤。我们将在下一部分讨论分类数据的特征工程策略!

统计变换

我们在前面简单地讨论了偏斜数据分布的不利影响。现在,让我们通过利用统计或数学变换来看看特征工程的不同策略。我们将研究对数变换以及 Box-Cox 变换。这两个转换函数都属于幂转换函数族,通常用于创建单调的数据转换。它们的主要意义在于,它们有助于稳定方差,紧密遵循正态分布,并使数据独立于基于其分布的均值

对数变换

对数变换属于幂变换函数族。该函数在数学上可以表示为

**其读作 x对数到底数 b 等于y。这可以转化为

其指示基底 b 必须提升到什么功率才能得到 x 。自然对数用 b=e 其中 e = 2.71828 俗称欧拉数。也可以使用十进制中普遍使用的基数 b =10。

对数变换在应用于偏斜分布时非常有用,因为它们倾向于扩展较低幅度范围内的值,并倾向于压缩或减少较高幅度范围内的值。这往往会使偏态分布尽可能接近正态分布。让我们在我们之前使用的开发人员Income特性上使用 log transform。

**fcc_survey_df['Income_log'] = np.log((1+ fcc_survey_df['Income']))
fcc_survey_df[['ID.x', 'Age', 'Income', 'Income_log']].iloc[4:9]**

Log transform on developer income

Income_log字段描述了对数变换后的变换特征。现在让我们来看看这个变换域上的数据分布。

**income_log_mean = np.round(np.mean(fcc_survey_df['Income_log']), 2)fig, ax = plt.subplots()
fcc_survey_df['Income_log'].hist(bins=30, color='#A9C5D3', 
                                 edgecolor='black', grid=False)
plt.axvline(income_log_mean, color='r')
ax.set_title('Developer Income Histogram after Log Transform', 
             fontsize=12)
ax.set_xlabel('Developer Income (log scale)', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.text(11.5, 450, r'$\mu$='+str(income_log_mean), fontsize=10)**

Histogram depicting developer income distribution after log transform

根据上面的图,我们可以清楚地看到,与原始数据的偏态分布相比,该分布更像正态分布或高斯分布。

博克斯-考克斯变换

Box-Cox 变换是属于幂变换函数族的另一个流行函数。该函数有一个先决条件,即要转换的数值必须是正的(类似于 log transform 所期望的)。如果它们是负的,使用常量值进行移位会有所帮助。数学上,Box-Cox 变换函数可以表示如下。

使得所得的变换输出 y 是输入 x 和变换参数λ的函数,使得当λ = 0 时,所得的变换是我们先前讨论的自然对数变换。λ的最佳值通常使用最大似然或对数似然估计来确定。现在让我们将 Box-Cox 变换应用于我们的开发者收入特性。首先,我们通过移除非空值从数据分布中获得最佳λ值,如下所示。

**income = np.array(fcc_survey_df['Income'])
income_clean = income[~np.isnan(income)]
l, opt_lambda = spstats.boxcox(income_clean)
print('Optimal lambda value:', opt_lambda) **Output
------**
Optimal lambda value: 0.117991239456**

现在我们已经获得了最佳λ值,让我们对λ的两个值使用 Box-Cox 变换,使得λ = 0 和λ = λ(最佳),并变换开发者Income特征。

**fcc_survey_df['Income_boxcox_lambda_0'] = spstats.boxcox(
                                        (1+fcc_survey_df['Income']), 
                                          lmbda=0)
fcc_survey_df['Income_boxcox_lambda_opt'] = spstats.boxcox(
                                            fcc_survey_df['Income'], 
                                              lmbda=opt_lambda)

fcc_survey_df[['ID.x', 'Age', 'Income', 'Income_log',                'Income_boxcox_lambda_0',                      'Income_boxcox_lambda_opt']].iloc[4:9]**

Developer income distribution after Box-Cox transform

上述数据框中描述了变换后的要素。正如我们所料,Income_logIncome_boxcox_lamba_0具有相同的值。让我们看看用最优λ变换后的变换后的Income特征的分布。

**income_boxcox_mean = np.round(
                      np.mean(
                       fcc_survey_df['Income_boxcox_lambda_opt']),2)fig, ax = plt.subplots()
fcc_survey_df['Income_boxcox_lambda_opt'].hist(bins=30, 
                     color='#A9C5D3', edgecolor='black', grid=False)
plt.axvline(income_boxcox_mean, color='r')
ax.set_title('Developer Income Histogram after Box–Cox Transform', 
             fontsize=12)
ax.set_xlabel('Developer Income (Box–Cox transform)', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.text(24, 450, r'$\mu$='+str(income_boxcox_mean), fontsize=10)**

Histogram depicting developer income distribution after Box-Cox transform

该分布看起来更正常——类似于我们在对数变换后获得的分布。

结论

特征工程是机器学习和数据科学的一个非常重要的方面,永远不应该被忽视。虽然我们有像深度学习这样的自动化功能工程方法,以及像 AutoML 这样的自动化机器学习框架(它仍然强调它需要好的功能才能很好地工作!).特征工程一直存在,甚至一些自动化方法通常需要基于数据类型、领域和要解决的问题的特定工程特征。

在本文中,我们研究了对连续数字数据进行特征工程的流行策略。在下一部分中,我们将研究处理离散、分类数据的流行策略,然后在以后的文章中讨论非结构化数据类型。敬请期待!

本文使用的所有代码和数据集都可以从我的 GitHub 访问

该代码也可作为 Jupyter 笔记本

分类数据

原文:towardsdatascience.com/understandi…

了解特征工程(第二部分)

处理离散分类数据的策略

Source: pixabay.com

介绍

我们在本系列的前一篇文章中介绍了处理结构化连续数字数据的各种特征工程策略。在本文中,我们将关注另一种类型的结构化数据,这种数据本质上是离散的,通常被称为分类数据。处理数字数据通常比分类数据更容易,因为我们不必处理与任何分类类型的数据属性中的每个类别值相关的额外的复杂语义。我们将使用实践方法来讨论处理分类数据的几种编码方案,以及处理大规模特征爆炸的几种流行技术,通常称为 【维数灾难】

动机

我相信到现在你一定意识到特征工程的动机和重要性,我们在本系列的 【第一部分】 中做了同样详细的强调。如果有必要的话,一定要检查一下,快速复习一下。简而言之,机器学习算法不能直接处理分类数据,在开始对数据建模之前,您需要对这些数据进行一些工程和转换。

理解分类数据

在深入特性工程策略之前,让我们先了解一下分类数据表示。通常,任何本质上是分类的数据属性都表示属于特定有限类别集的离散值。在由模型预测的属性或变量(通常称为响应变量)的上下文中,这些通常也称为类或标签。这些离散值本质上可以是文本或数字(甚至是像图像这样的非结构化数据!).分类数据有两大类,名义数据和序数数据。

在任何名义分类数据属性中,该属性的值之间没有排序的概念。考虑一个简单的天气类别示例,如下图所示。我们可以看到,在这个特定的场景中,我们有六个主要的类别或种类,没有任何顺序的概念或概念(多风并不总是发生在晴朗之前,也不小于或大于晴朗*)。*

Weather as a categorical attribute

类似地,电影、音乐和视频游戏类型、国家名称、食物和烹饪类型是其他名义分类属性的例子。

有序分类属性在其值中具有某种意义或顺序概念。例如,请看下图中的衬衫尺寸。很明显,当考虑衬衫时,顺序或者在这种情况下的‘size’很重要( S 小于 M ,M 小于 L 等等)。

Shirt size as an ordinal categorical attribute

鞋号、教育水平和就业角色是有序分类属性的一些其他示例。对分类数据有了一个很好的概念,现在让我们看看一些特征工程策略。

分类数据的特征工程

虽然在各种机器学习框架中已经取得了很多进步,以接受复杂的分类数据类型,如文本标签。典型地,特征工程中的任何标准工作流程都涉及将这些分类值的某种形式的 转换 为数字标签,然后对这些值应用某种 编码方案 。开始之前,我们把必要的必需品都装了起来。

*import pandas as pd
import numpy as np*

转换名义属性

名义属性由离散的分类值组成,它们之间没有概念或顺序感。这里的想法是将这些属性转换成更具代表性的数字格式,以便下游代码和管道能够轻松理解。让我们来看一个关于视频游戏销售的新数据集。这个数据集也可以在以及我的GitHub资源库中找到。

*****vg_df = pd.read_csv('datasets/vgsales.csv', encoding='utf-8')
vg_df[['Name', 'Platform', 'Year', 'Genre', 'Publisher']].iloc[1:7]*****

Dataset for video game sales

让我们关注上面数据框中描述的视频游戏Genre属性。很明显,这是一个名词性的范畴属性,就像PublisherPlatform一样。我们可以很容易地得到独特的视频游戏类型列表如下。

*****genres = np.unique(vg_df['Genre'])
genres**Output
------**
array(['Action', 'Adventure', 'Fighting', 'Misc', 'Platform',         'Puzzle', 'Racing', 'Role-Playing', 'Shooter', 'Simulation',         'Sports', 'Strategy'], dtype=object)*****

这告诉我们,我们有 12 种不同的视频游戏类型。我们现在可以生成一个标签编码方案,通过利用scikit-learn将每个类别映射到一个数值。

*****from sklearn.preprocessing import LabelEncodergle = LabelEncoder()
genre_labels = gle.fit_transform(vg_df['Genre'])
genre_mappings = {index: label for index, label in 
                  enumerate(gle.classes_)}
genre_mappings **Output
------** {0: 'Action', 1: 'Adventure', 2: 'Fighting', 3: 'Misc',
 4: 'Platform', 5: 'Puzzle', 6: 'Racing', 7: 'Role-Playing',
 8: 'Shooter', 9: 'Simulation', 10: 'Sports', 11: 'Strategy'}*****

因此,在LabelEncoder对象gle的帮助下,生成了一个映射方案,其中每个流派值被映射到一个数字。转换后的标签存储在genre_labels值中,我们可以将该值写回到我们的数据框中。

*****vg_df['GenreLabel'] = genre_labels
vg_df[['Name', 'Platform', 'Year', 'Genre', 'GenreLabel']].iloc[1:7]*****

Video game genres with their encoded labels

如果您计划将这些标签用作预测的响应变量,则可以直接使用这些标签,尤其是在像scikit-learn这样的框架中,但是如前所述,在将它们用作特性之前,我们需要对它们进行额外的编码。

转换序数属性

序数属性是在值之间有顺序感的分类属性。让我们考虑一下我们的 神奇宝贝数据集 ,我们在本系列的 第 1 部分 中使用过。让我们更具体地关注一下Generation属性。

*****poke_df = pd.read_csv('datasets/Pokemon.csv', encoding='utf-8')
poke_df = poke_df.sample(random_state=1, 
                         frac=1).reset_index(drop=True)np.unique(poke_df['Generation'])**Output
------**
array(['Gen 1', 'Gen 2', 'Gen 3', 'Gen 4', 'Gen 5', 'Gen 6'], 
         dtype=object)*****

根据上面的输出,我们可以看到总共有 6 代,每个神奇宝贝通常都属于基于视频游戏的特定一代(当它们被发布时),电视连续剧也遵循类似的时间线。这个属性通常是顺序的(领域知识在这里是必要的),因为大多数属于第 1 代的神奇宝贝在视频游戏和电视节目中比第 2 代更早出现,等等。粉丝们可以看看下图,记住每一代流行的一些神奇宝贝(粉丝们的看法可能会有所不同!).

Popular Pokémon based on generation and type (source: www.reddit.com/r/pokemon/c…)

因此他们有一种秩序感。一般来说,没有通用的模块或函数来根据订单自动将这些特征映射和转换成数字表示。因此,我们可以使用自定义编码\映射方案。

*****gen_ord_map = {'Gen 1': 1, 'Gen 2': 2, 'Gen 3': 3, 
               'Gen 4': 4, 'Gen 5': 5, 'Gen 6': 6}poke_df['GenerationLabel'] = poke_df['Generation'].map(gen_ord_map)
poke_df[['Name', 'Generation', 'GenerationLabel']].iloc[4:10]*****

Pokémon generation encoding

从上面的代码中可以明显看出,pandas中的map(…)函数在转换这个顺序特性时很有帮助。

编码分类属性

如果您还记得我们之前提到的内容,分类数据的特征工程通常包括我们在上一节中描述的转换过程和强制编码过程,在该过程中,我们应用特定的编码方案为特定分类属性中的每个类别\值创建虚拟变量或特征。

您可能想知道,我们在前面的部分中刚刚将类别转换为数字标签,现在我们究竟为什么需要它呢?原因很简单。考虑到视频游戏流派,如果我们直接将GenreLabel属性作为机器学习模型中的一个特征,它会认为它是一个连续的数字特征,认为值 10 ( 体育)大于 6 ( 赛车),但这是没有意义的,因为体育流派肯定不会大于或小于赛车,这些是本质上不同的值或类别,不能直接进行比较。因此,我们需要一个额外的编码方案层,其中为每个属性的所有不同类别中的每个唯一值或类别创建虚拟特征。

独热编码方案

考虑到我们具有带有 m 标签的任何分类属性的数字表示(转换后),一键编码方案将属性编码或转换为 m 二进制特征,其只能包含值 1 或 0。因此,分类特征中的每个观察结果都被转换成大小为 m 的向量,其中只有一个值为 1 (表示它是活动的)。让我们取一个描述两个感兴趣的属性的神奇宝贝数据集的子集。

*****poke_df[['Name', 'Generation', 'Legendary']].iloc[4:10]*****

Subset of our Pokémon dataset

感兴趣的属性是神奇宝贝Generation和它们的Legendary状态。第一步是这些属性转换成基于我们之前所学的数字表示。

*****from sklearn.preprocessing import OneHotEncoder, LabelEncoder# transform and map pokemon generations
gen_le = LabelEncoder()
gen_labels = gen_le.fit_transform(poke_df['Generation'])
poke_df['Gen_Label'] = gen_labels# transform and map pokemon legendary status
leg_le = LabelEncoder()
leg_labels = leg_le.fit_transform(poke_df['Legendary'])
poke_df['Lgnd_Label'] = leg_labelspoke_df_sub = poke_df[['Name', 'Generation', 'Gen_Label',  
                       'Legendary', 'Lgnd_Label']]
poke_df_sub.iloc[4:10]*****

Attributes with transformed (numeric) labels

特征Gen_LabelLgnd_Label现在描述了我们的分类特征的数字表示。现在让我们对这些特性应用一键编码方案。

*****# encode generation labels using one-hot encoding scheme
gen_ohe = OneHotEncoder()
gen_feature_arr = gen_ohe.fit_transform(
                              poke_df[['Gen_Label']]).toarray()
gen_feature_labels = list(gen_le.classes_)
gen_features = pd.DataFrame(gen_feature_arr, 
                            columns=gen_feature_labels)# encode legendary status labels using one-hot encoding scheme
leg_ohe = OneHotEncoder()
leg_feature_arr = leg_ohe.fit_transform(
                                poke_df[['Lgnd_Label']]).toarray()
leg_feature_labels = ['Legendary_'+str(cls_label) 
                           for cls_label in leg_le.classes_]
leg_features = pd.DataFrame(leg_feature_arr, 
                            columns=leg_feature_labels)*****

一般来说,你总是可以使用fit_transform(…)函数将两个特征编码在一起,方法是将两个特征的二维数组传递给它(查看文档!).但是我们分别对每个特性进行编码,以便于理解。除此之外,我们还可以创建单独的数据框,并对其进行相应的标注。现在让我们连接这些特征框架,看看最终的结果。

*****poke_df_ohe = pd.concat([poke_df_sub, gen_features, leg_features], axis=1)
columns = sum([['Name', 'Generation', 'Gen_Label'],   
               gen_feature_labels, ['Legendary', 'Lgnd_Label'], 
               leg_feature_labels], [])
poke_df_ohe[columns].iloc[4:10]*****

One-hot encoded features for Pokémon generation and legendary status

因此,您可以看到为Generation创建了 6 虚拟变量或二进制特征,为Legendary创建了 2 虚拟变量或二进制特征,因为它们分别是这些属性中不同类别的总数。类别的 活动 状态由这些虚拟变量之一的 1 值表示,从上述数据帧中可以明显看出。

假设您在训练数据上建立了这种编码方案,并建立了一些模型,现在您有一些新数据,这些数据必须在预测之前针对如下特征进行设计。

*****new_poke_df = pd.DataFrame([['PikaZoom', 'Gen 3', True], 
                           ['CharMyToast', 'Gen 4', False]],
                       columns=['Name', 'Generation', 'Legendary'])
new_poke_df*****

Sample new data

在这里,您可以通过对新数据调用先前构建的LabeLEncoderOneHotEncoder 对象的transform(…)函数来利用scikit-learn’s优秀的 API。记住我们的工作流程,首先我们做 转换

*****new_gen_labels = gen_le.transform(new_poke_df['Generation'])
new_poke_df['Gen_Label'] = new_gen_labelsnew_leg_labels = leg_le.transform(new_poke_df['Legendary'])
new_poke_df['Lgnd_Label'] = new_leg_labelsnew_poke_df[['Name', 'Generation', 'Gen_Label', 'Legendary',              'Lgnd_Label']]*****

Categorical attributes after transformation

一旦我们有了数字标签,现在让我们应用编码方案!

*****new_gen_feature_arr = gen_ohe.transform(new_poke_df[['Gen_Label']]).toarray()
new_gen_features = pd.DataFrame(new_gen_feature_arr, 
                                columns=gen_feature_labels)new_leg_feature_arr = leg_ohe.transform(new_poke_df[['Lgnd_Label']]).toarray()
new_leg_features = pd.DataFrame(new_leg_feature_arr, 
                                columns=leg_feature_labels)new_poke_ohe = pd.concat([new_poke_df, new_gen_features, new_leg_features], axis=1)
columns = sum([['Name', 'Generation', 'Gen_Label'], 
               gen_feature_labels,
               ['Legendary', 'Lgnd_Label'], leg_feature_labels], [])new_poke_ohe[columns]*****

Categorical attributes after one-hot encoding

因此,您可以看到,通过利用scikit-learn’s强大的 API,可以很容易地在新数据上应用这个方案。

您还可以通过利用pandas中的to_dummies(…)函数轻松应用一键编码方案。

*****gen_onehot_features = pd.get_dummies(poke_df['Generation'])
pd.concat([poke_df[['Name', 'Generation']], gen_onehot_features], 
           axis=1).iloc[4:10]*****

One-hot encoded features by leveraging pandas

上述数据框描述了应用于Generation属性的独热编码方案,其结果与之前的预期结果相同。

虚拟编码方案

伪编码方案类似于独热编码方案,除了在伪编码方案的情况下,当应用于具有 m 个不同标签的分类特征时,我们得到 m - 1 个二进制特征。因此,分类变量的每个值都被转换成大小为 m - 1 的向量。额外的特征被完全忽略,因此如果类别值的范围从 {0,1,…,m-1 }第 0 个m-1 个 特征列被丢弃,相应的类别值通常由全零的矢量 (0) 表示。让我们通过删除第一级二进制编码特征(Gen 1)来尝试在神奇宝贝Generation 上应用虚拟编码方案。

*****gen_dummy_features = pd.get_dummies(poke_df['Generation'], 
                                    drop_first=True)
pd.concat([poke_df[['Name', 'Generation']], gen_dummy_features], 
          axis=1).iloc[4:10]*****

Dummy coded features for Pokémon generation

如果你愿意,你也可以选择丢弃最后一级二进制编码特征(Gen 6)如下。

*****gen_onehot_features = pd.get_dummies(poke_df['Generation'])
gen_dummy_features = gen_onehot_features.iloc[:,:-1]
pd.concat([poke_df[['Name', 'Generation']], gen_dummy_features],  
          axis=1).iloc[4:10]*****

Dummy coded features for Pokémon generation

基于上面的描述,很清楚属于丢弃特征的类别被表示为零向量( 0) ,就像我们之前讨论的那样。

效果编码方案

效果编码方案实际上与虚拟编码方案非常相似,只是在编码过程中,虚拟编码方案中代表所有 0 的类别值的编码特征或特征向量在效果编码方案中被替换为 -1 。通过下面的例子,这将变得更加清楚。

*****gen_onehot_features = pd.get_dummies(poke_df['Generation'])
gen_effect_features = gen_onehot_features.iloc[:,:-1]
gen_effect_features.loc[np.all(gen_effect_features == 0, 
                               axis=1)] = -1.
pd.concat([poke_df[['Name', 'Generation']], gen_effect_features], 
          axis=1).iloc[4:10]*****

Effect coded features for Pokémon generation

上面的输出清楚地表明,与虚拟编码中的 0 相比,属于Generation 6 的神奇宝贝现在由值为-1 的向量表示。

面元计数方案

到目前为止,我们讨论的编码方案在一般分类数据上工作得很好,但是当任何特征中不同类别的数量变得非常大时,它们就开始产生问题。对任何一个绝对特征都必不可少的 m 不同的标签,你就得到 m 单独的特征。这很容易增加特征集的大小,从而导致诸如存储问题、关于时间、空间和内存的模型训练问题之类的问题。除此之外,我们还必须处理通常所说的 【维数灾难】等问题,在这些问题中,基本上有大量的特征和没有足够的代表性样本,模型性能开始受到影响,经常导致过度拟合。

因此,对于具有大量可能类别(如 IP 地址)的特征,我们需要寻找其他分类数据特征工程方案。容器计数方案对于处理具有许多类别的分类变量是一种有用的方案。在该方案中,我们使用基于概率的关于值和实际目标或响应值的统计信息,而不是使用实际标签值进行编码,我们的目标是在建模工作中预测这些信息。一个简单的例子是基于 IP 地址和 DDOS 攻击中使用的 IP 地址的历史数据;我们可以为由任何 IP 地址引起的 DDOS 攻击建立概率值。使用该信息,我们可以对输入特征进行编码,该输入特征描述了如果相同的 IP 地址在将来出现,导致 DDOS 攻击的概率值是多少。这个方案需要历史数据作为先决条件,是一个精心制作的方案。用一个完整的例子来描述这一点目前是困难的,但是你可以参考网上的一些资源。

特征散列方案

特征散列方案是另一种用于处理大规模分类特征的有用的特征工程方案。在该方案中,散列函数通常与预设的编码特征数量(作为预定义长度的向量)一起使用,使得特征的散列值被用作该预定义向量中的索引,并且值被相应地更新。由于哈希函数将大量值映射到一个有限的小值集,多个不同的值可能会创建相同的哈希,这称为冲突。通常,使用带符号的散列函数,使得从散列中获得的值的符号被用作存储在最终特征向量中适当索引处的值的符号。这将确保更少的冲突和由于冲突导致的更少的误差累积。

哈希方案适用于字符串、数字和其他结构,如向量。您可以将哈希输出视为一组有限的 b 二进制文件,这样,当哈希函数应用于相同的值\类别时,它们会根据哈希值被分配到 b 二进制文件中的相同二进制文件(或二进制文件的子集)。我们可以预先定义 b 的值,该值成为我们使用特征散列方案编码的每个分类属性的编码特征向量的最终大小。

因此,即使我们在一个特征中有超过 1000 个不同的类别,并且我们将 b=10 设置为最终的特征向量大小,如果我们使用一位热码编码方案,则输出特征集将仍然只有 10 个特征,而不是 1000 个二进制特征。让我们考虑一下视频游戏数据集中的Genre属性。

******unique_genres = np.unique(vg_df[['Genre']])
print("Total game genres:", len(unique_genres))
print(unique_genres)**Output
------**
Total game genres: 12
['Action' 'Adventure' 'Fighting' 'Misc' 'Platform' 'Puzzle' 'Racing'
 'Role-Playing' 'Shooter' 'Simulation' 'Sports' 'Strategy']******

我们可以看到一共有 12 种类型的电子游戏。如果我们在Genre 特性上使用一个独热编码方案,我们最终会有 12 个二进制特性。相反,我们现在将通过利用scikit-learn’s FeatureHasher类来使用一个特性散列方案,它使用一个带符号的 32 位版本的 Murmurhash3 散列函数。在这种情况下,我们将预先定义最终的特征向量大小为 6

******from sklearn.feature_extraction import FeatureHasherfh = FeatureHasher(n_features=6, input_type='string')
hashed_features = fh.fit_transform(vg_df['Genre'])
hashed_features = hashed_features.toarray()
pd.concat([vg_df[['Name', 'Genre']], pd.DataFrame(hashed_features)], 
          axis=1).iloc[1:7]******

Feature Hashing on the Genre attribute

基于上述输出,Genre 分类属性已经使用哈希方案编码成 6 特征,而不是 12 。我们还可以看到,行 16 表示相同流派的游戏, 平台 被正确地编码到相同的特征向量中。

结论

这些例子应该让您对离散、分类数据的特征工程的流行策略有一个很好的了解。如果你阅读了本系列的第一部分 ,你会发现与连续的数字数据相比,处理分类数据有点困难,但是绝对有趣!我们还讨论了一些使用特征工程来处理大特征空间的方法,但是你也应该记住还有其他的技术,包括 特征选择降维 方法来处理大特征空间。我们将在后面的文章中讨论其中的一些方法。

接下来是针对非结构化文本数据的特征工程策略。敬请期待!

要了解连续数值数据的特征工程策略,请查看本系列的第 1 部分****

本文中使用的所有代码和数据集都可以从我的 GitHub 中获得

该代码也可作为 Jupyter 笔记本

文本数据的传统方法

原文:towardsdatascience.com/understandi…

了解特征工程(第三部分)

驯服非结构化文本数据的传统策略

介绍

在本系列*的前两部分中,我们已经介绍了处理结构化数据的各种特性工程策略。*检查第一部分:连续的数值数据第二部分:离散的分类数据复习。在本文中,我们将了解如何处理文本数据,这无疑是最丰富的非结构化数据来源之一。文本数据通常由能够代表自由流动文本的单词、句子甚至段落的文档组成。固有的非结构化(没有格式整齐的数据列!)和文本数据的噪声性质使得机器学习方法更难直接在原始文本数据上工作。因此,在本文中,我们将遵循动手实践的方法,探索一些最流行和最有效的从文本数据中提取有意义特征的策略。这些特征可以很容易地用于建立机器学习或深度学习模型。

动机

特征工程通常被认为是创造卓越和性能更好的机器学习模型的秘方。仅仅一个优秀的功能就可能成为你赢得挑战的入场券!对于非结构化的文本数据,特征工程的重要性甚至更加重要,因为我们需要将自由流动的文本转换为一些数字表示,然后机器学习算法可以理解这些数字表示。即使有了自动化特征工程能力的出现,在将它们作为黑盒模型应用之前,您仍然需要理解不同特征工程策略背后的核心概念。永远记住,“如果给你一盒修理房子的工具,你要知道什么时候用电钻,什么时候用锤子!”

理解文本数据

我敢肯定,在这种情况下,你们所有人对文本数据的组成都有一个大致的概念。请记住,您总是可以拥有结构化数据属性形式的文本数据,但是这些数据通常属于结构化分类数据的范畴。

在这个场景中,我们谈论的是单词、短语、句子和整个文档形式的自由流动的文本。本质上,我们有一些句法结构,像单词组成短语,短语组成句子,句子又组成段落。然而,文本文档没有固有的结构,因为您可能有各种各样的单词,这些单词可能在文档之间有所不同,并且与结构化数据集中固定数量的数据维度相比,每个句子的长度也是可变的。这篇文章本身就是一个完美的文本数据例子!

特征工程策略

让我们看看一些流行而有效的策略,用于处理文本数据并从中提取有意义的特征,这些策略可用于下游的机器学习系统。请注意,您可以在我的 GitHub 库 中访问本文中使用的所有代码,以供将来参考。我们将从加载一些基本的依赖项和设置开始。

*import pandas as pd
import numpy as np
import re
import nltk
import matplotlib.pyplot as pltpd.options.display.max_colwidth = 200
%matplotlib inline*

现在让我们来看一个样本文档集,我们将在本文中对其进行大部分分析。 语料库 通常是通常属于一个或多个主题的文本文档的集合。

Our sample text corpus

您可以看到,我们已经为我们的玩具语料库提取了几个属于不同类别的样本文本文档。在我们谈论特征工程之前,一如既往,我们需要做一些数据预处理或争论,以删除不必要的字符,符号和令牌。

文本预处理

可以有多种清理和预处理文本数据的方式。在下面的几个要点中,我们强调了自然语言处理(NLP)管道中大量使用的一些最重要的方法。

  • ***去除标签:*我们的文本往往包含 HTML 标签这样不必要的内容,在分析文本的时候并没有增加多少价值。BeautifulSoup 库在为此提供必要的函数方面做得非常出色。
  • 删除带重音的字符:在任何文本语料库中,尤其是在处理英语语言时,经常会遇到带重音的字符\字母。因此,我们需要确保这些字符被转换并标准化为 ASCII 字符。一个简单的例子就是将转换成再转换成 e
  • ***扩展缩写:*在英语中,缩写基本上是单词或音节的缩短版本。这些现有单词或短语的缩短版本是通过删除特定的字母和声音创建的。例如, 不要不要我要我要 。将每个缩写转换为其扩展的原始形式通常有助于文本标准化。
  • ***去除特殊字符:*通常为非字母数字字符的特殊字符和符号通常会增加非结构化文本中的额外噪声。更常见的是,简单的正则表达式(regexes)可以用来实现这一点。
  • 词干和词汇化:**词干通常是可能单词的基本形式,可以通过将词缀前缀后缀附加到词干来创建新单词。这就是所谓的变调。获得单词基本形式的反向过程被称为词干提取。一个简单的例子就是 手表 ES手表 ING手表 ED 这几个词。他们以词根词干 为基础形式。词汇化与词干化非常相似,我们移除词缀来获得单词的基本形式。然而,在这种情况下,基本形式被称为词根,而不是词干。区别在于词根总是字典上正确的单词(存在于字典中),但是词干可能不是这样。
  • 去除停用词:**意义不大或没有意义的词,尤其是在从文本中构建有意义的特征时,被称为停用词或停用词。如果你在语料库中做一个简单的术语或词频,这些通常是出现频率最高的词。像 aa等单词都被认为是停用词。没有通用的停用词表,但是我们使用来自nltk的标准英语停用词表。您也可以根据需要添加自己的特定领域的停用词。

除此之外,您还可以进行其他标准操作,如标记化、删除多余的空格、文本小写以及更高级的操作,如拼写纠正、语法错误纠正、删除重复字符等。如果你有兴趣,你可以从我最近的一本书中查阅 一个关于文本预处理 的样本笔记本。

由于本文的重点是特征工程,我们将构建一个简单的文本预处理程序,重点是删除文本语料库中的特殊字符、多余空格、数字、停用词和小写字母。

一旦我们准备好基本的预处理管道,让我们将同样的应用到我们的样本语料库。

***norm_corpus = normalize_corpus(corpus)
norm_corpus**Output
------** array(['sky blue beautiful', 'love blue beautiful sky',
       'quick brown fox jumps lazy dog',
       'kings breakfast sausages ham bacon eggs toast beans',
       'love green eggs ham sausages bacon',
       'brown fox quick blue dog lazy', 
       'sky blue sky beautiful today',
       'dog lazy brown fox quick'],
      dtype='<U51')***

上面的输出应该让您清楚地看到我们的每个样本文档在预处理后的样子。现在让我们来设计一些功能吧!

词汇袋模型

对于非结构化文本,这可能是最简单的向量空间表示模型。向量空间模型是一个简单的数学模型,将非结构化文本(或任何其他数据)表示为数字向量,这样向量的每个维度都是一个特定的特征\属性。单词袋模型将每个文本文档表示为数字向量,其中每个维度是来自语料库的特定单词,并且该值可以是它在文档中的频率、出现次数(用 1 或 0 表示)或者甚至是加权值。这个模型之所以叫这个名字,是因为每个文档都被字面上表示为它自己的单词的“包”,而不考虑单词顺序、序列和语法。

*****Output
------**
array([[0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
       [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0],
       [0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0],
       [1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0],
       [1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0],
       [0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
       [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1],
       [0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0]
      ], dtype=int64)***

因此,您可以看到我们的文档已经被转换成数字向量,这样,在上面的特征矩阵中,每个文档都由一个向量(行)表示。下面的代码将有助于用一种更容易理解的格式来表示这一点。

Our Bag of Words model based document feature vectors

这应该会让事情更清楚吧!您可以清楚地看到,特征向量中的每一列或每一个维度代表语料库中的一个单词,每一行代表我们的一个文档。任何单元格中的值表示该单词(由列表示)在特定文档(由行表示)中出现的次数。因此,如果文档集由跨越所有文档的 N 个 唯一单词组成,那么对于每个文档,我们将有一个 N 维 向量。

N 克模型袋

一个单词只是一个标记,通常被称为一元语法或一元语法。我们已经知道单词袋模型不考虑单词的顺序。但是如果我们也想考虑出现在序列中的短语或单词集合呢?N-grams 帮助我们实现了这一点。N 元语法基本上是来自文本文档的单词标记的集合,使得这些标记是连续的并且按顺序出现。二元语法表示 2 阶的 n 元语法(两个单词),三元语法表示 3 阶的 n 元语法(三个单词),依此类推。因此,N-gram 模型只是单词模型的扩展,所以我们也可以利用基于 N-gram 的特征。下面的例子描述了每个文档特征向量中基于二元语法的特征。

Bi-gram based feature vectors using the Bag of N-Grams Model

这为我们的文档提供了特征向量,其中每个特征由表示两个单词序列的二元语法组成,值表示该二元语法在我们的文档中出现的次数。

TF-IDF 模型

词袋模型在用于大型语料库时可能会出现一些潜在的问题。因为特征向量是基于绝对术语频率的,所以可能有一些术语在所有文档中频繁出现,并且这些术语可能会盖过特征集中的其他术语。TF-IDF 模型试图通过在其计算中使用一个缩放或归一化因子来解决这个问题。TF-IDF 代表词频-逆文档频率,它在 的计算中使用了两个度量的组合,即: 【词频】逆文档频率(idf) 。这项技术是为搜索引擎中的查询结果排序而开发的,现在它是信息检索和 NLP 领域中不可或缺的模型。

数学上,我们可以将 TF-IDF 定义为 tfidf = tf x idf ,可以进一步展开表示如下。

这里, tfidf(w,D) 是文档中单词 w 的 TF-IDF 得分。术语 tf(w,D) 表示单词 w 在文档 D 中的词频,可以从词袋模型中得到。术语 idf(w,D) 是术语 w 的逆文档频率,其可以计算为语料库 C 中的文档总数除以单词 w 的文档频率的对数变换,这基本上是单词 w 在语料库中的文档频率这个模型有多种变体,但最终都给出了非常相似的结果。现在让我们把它应用到我们的语料库中吧!

Our TF-IDF model based document feature vectors

我们的每个文本文档的基于 TF-IDF 的特征向量显示了与原始单词包模型值相比的缩放和归一化值。有兴趣的读者可能想深入了解这个模型内部工作原理的更多细节,可以参考第 181 页的 使用 Python 的文本分析(Springer \ ApressDipanjan Sarkar,2016)

文档相似度

文档相似性是使用基于距离或相似性的度量的过程,该度量可用于基于从文档中提取的特征(如单词袋或 tf-idf)来识别文本文档与任何其他文档的相似程度。

Are we similar?

因此,您可以看到,我们可以在上一节中设计的基于 tf-idf 的功能的基础上进行构建,并通过利用这些基于相似性的功能,使用它们来生成在搜索引擎、文档聚类和信息检索等领域中有用的新功能。

语料库中的成对文档相似性涉及计算语料库中每对文档的文档相似性。因此,如果在一个语料库中有 C 个文档,那么最终会得到一个 C x C 矩阵,使得每一行和每一列都代表一对文档的相似性得分,这分别代表该行和该列的索引。有几种用于计算文档相似性的相似性和距离度量。其中包括余弦距离/相似度、欧氏距离、曼哈顿距离、BM25 相似度、jaccard 距离等等。在我们的分析中,我们将使用可能是最流行和最广泛使用的相似性度量, 余弦相似性,并基于它们的 TF-IDF 特征向量来比较成对的文档相似性。

Pairwise document similarity matrix (cosine similarity)

余弦相似性基本上给出了一种度量,表示两个文本文档的特征向量表示之间的角度的余弦。文档之间的角度越小,它们就越接近和相似,如下图所示。

Cosine similarity depictions for text document feature vectors

仔细观察相似性矩阵清楚地告诉我们,文档(0、1 和 6)、(2、5 和 7)彼此非常相似,文档 3 和 4 彼此稍微相似,但是幅度不是很大,但是仍然比其他文档强。这一定表明这些相似的文件有一些相似的特征。这是一个分组或聚类的完美例子,可以通过无监督学习来解决,特别是当您处理数百万文本文档的庞大语料库时。

基于相似特征的文档聚类

聚类利用无监督学习将数据点(在这个场景中是文档)分组到组或聚类中。我们将在这里利用无监督的层次聚类算法,通过利用我们之前生成的文档相似性特征,尝试将来自我们的玩具语料库的相似文档分组在一起。有两种类型的层次聚类算法,即凝聚和分裂方法。我们将使用凝聚聚类算法,这是一种使用自下而上方法的分层聚类,即每个观察或文件从其自己的聚类开始,然后使用测量数据点之间距离的距离度量和链接合并标准将聚类连续合并在一起。下图显示了一个示例描述。

Agglomerative Hierarchical Clustering

关联标准的选择决定了合并策略。连锁标准的一些例子是沃德、完全连锁、平均连锁等。该标准对于在每个步骤中基于目标函数的最优值选择要合并的聚类对(最低步骤中的单个文档和较高步骤中的聚类)非常有用。我们选择沃德的最小方差方法作为我们的连锁标准,以最小化总的组内方差。因此,在每一步,我们找到合并后导致总的类内方差最小增加的那对类。因为我们已经有了相似性特征,所以让我们在样本文档上构建链接矩阵。

Linkage Matrix for our Corpus

如果仔细观察关联矩阵,您会发现关联矩阵的每一步(每一行)都告诉我们哪些数据点(或聚类)被合并在一起。如果你有 n 个数据点,那么链接矩阵, Z 将具有*(n-1)x4其中将告诉我们哪些聚类在步骤 i 被合并。每行有四个元素,前两个元素是数据点标识符或聚类标签(在矩阵的后面部分,一旦 多个数据点合并),第三个元素是前两个元素(数据点或聚类)之间的聚类距离,最后一个元素是合并完成后聚类中元素\数据点的总数。我们推荐你参考 scipy 文档 ,里面对此有详细解释。*

现在让我们把这个矩阵想象成一个树状图,以便更好地理解这些元素!

Dendrogram visualizing our hierarchical clustering process

我们可以看到每个数据点如何开始作为一个单独的集群,并慢慢开始与其他数据点合并形成集群。从颜色和树状图的高度来看,如果您考虑大约 1.0 或更高的距离度量(由虚线表示),您可以看到该模型已经正确地识别了三个主要聚类。利用这个距离,我们得到我们的聚类标签。

Clustering our documents into groups with hierarchical clustering

因此,您可以清楚地看到,我们的算法已经根据分配给它们的聚类标签,在我们的文档中正确地识别了三个不同的类别。这将让您很好地了解如何利用我们的 TF-IDF 特性来构建我们的相似性特性,这反过来又有助于对我们的文档进行聚类。您实际上可以在将来使用这个管道来聚集您自己的文档!

主题模型

我们还可以使用一些摘要技术从文本文档中提取基于主题或概念的特征。主题模型的思想围绕着从表示为主题的文档语料库中提取关键主题或概念的过程。每个主题可以被表示为来自文档语料库的单词/术语的包或集合。总的来说,这些术语表示特定的主题、主题或概念,并且每个主题可以通过这些术语所传达的语义来容易地与其他主题区分开来。然而,基于这些数据,你经常会得到一些重叠的主题。这些概念可以从简单的事实和陈述到观点和展望。主题模型在总结大量文本文档以提取和描述关键概念时非常有用。它们还可用于从文本数据中提取特征,以捕捉数据中的潜在模式。

An example of topic models

主题建模有多种技术,其中大多数都涉及某种形式的矩阵分解。像潜在语义索引(LSI)这样的一些技术使用矩阵分解操作,更具体地说是奇异值分解。我们将使用另一种技术是潜在狄利克雷分配(LDA),它使用一种生成概率模型,其中每个文档由几个主题的组合组成,每个术语或单词可以分配给一个特定的主题。这类似于基于 pLSI 的模型(概率 LSI)。在 LDA 的情况下,每个潜在主题包含一个 Dirichlet 先验。

这项技术背后的数学是相当复杂的,所以我会尽量总结一下,而不会用很多细节来烦你。我推荐读者去看看克里斯汀·多伊格的这篇精彩的演讲。****

End-to-end LDA framework (courtesy of C. Doig, Introduction to Topic Modeling in Python)

上图中的黑框表示核心算法,该算法利用前面提到的参数从 M 文档中提取 K 主题。下面的步骤简单地解释了算法在幕后发生了什么。

运行几次迭代后,我们应该为每个文档准备好主题混合,然后从指向该主题的术语中生成每个主题的成分。像gensimscikit-learn这样的框架使我们能够利用 LDA 模型来生成主题。

出于特征工程的目的(这也是本文的目的),您需要记住,当 LDA 应用于文档术语矩阵(TF-IDF 或单词包特征矩阵)时,它被分解为两个主要部分。

  • 文档-主题矩阵,这将是我们正在寻找的特征矩阵。
  • 一个主题术语矩阵,帮助我们在语料库中寻找潜在的主题。

让我们利用scikit-learn得到如下的文档-主题矩阵。

Document-Topic Matrix from our LDA Model

在上面的输出中,您可以清楚地看到哪些文档对三个主题中的哪一个贡献最大。您可以按如下方式查看主题及其主要组成部分。

******Topic 1
-------**
[('sky', 4.3324395825632624), ('blue', 3.3737531748317711), ('beautiful', 3.3323652405224857), ('today', 1.3325579841038182), ('love', 1.3304224288080069)]**Topic 2
-------**
[('bacon', 2.3326959484799978), ('eggs', 2.3326959484799978), ('ham', 2.3326959484799978), ('sausages', 2.3326959484799978), ('love', 1.335454457601996), ('beans', 1.3327735253784641), ('breakfast', 1.3327735253784641), ('kings', 1.3327735253784641), ('toast', 1.3327735253784641), ('green', 1.3325433207547732)]**Topic 3
-------** [('brown', 3.3323474595768783), ('dog', 3.3323474595768783), ('fox', 3.3323474595768783), ('lazy', 3.3323474595768783), ('quick', 3.3323474595768783), ('jumps', 1.3324193736202712), ('blue', 1.2919635624485213)]****

因此,你可以清楚地看到,这三个主题根据它们的构成术语彼此有很大的区别,第一个谈论天气,第二个谈论食物,最后一个谈论动物。选择主题建模的主题数量本身就是一个完整的主题(不是双关语!)是一门艺术,也是一门科学。有各种各样的方法和试探法来获得最佳的主题数量,但是由于这些技术的详细性质,我们在这里不讨论它们。

具有主题模型特征的文档聚类

我们使用基于特征的词袋模型,使用 LDA 建立基于特征的主题模型。我们现在实际上可以利用我们获得的文档术语矩阵,并使用无监督聚类算法来尝试对我们的文档进行分组,就像我们之前使用相似性特征所做的那样。

这一次,我们将使用一种非常流行的基于划分的聚类方法,K-means 聚类来根据这些文档的主题模型特征表示对它们进行聚类或分组。在 K-means 聚类中,我们有一个输入参数 k ,它指定了使用文档特征输出的聚类数。这种聚类方法是一种基于质心的聚类方法,它试图将这些文档聚类成等方差的聚类。它试图通过最小化类内平方和度量(也称为惯性)来创建这些类。有多种方法来选择 k 的最佳值,如使用误差平方和度量、轮廓系数和肘方法。

Clustering our documents into groups with K-means clustering

从上面的输出中我们可以看到,我们的文档被正确地分配到了正确的集群中!

高级策略的未来范围

我们在本文中没有涉及的是围绕文本数据的特征工程的几个高级策略,这些策略最近变得很突出。这包括利用基于深度学习的模型来获得单词嵌入。在本系列的下一部分中,我们将深入探讨这些模型,并通过详细的实践示例介绍流行的单词嵌入模型,如word 2 vecGloVe,敬请关注!

结论

这些例子应该让您对文本数据的特征工程的流行策略有一个很好的了解。请记住,这些是基于数学、信息检索和自然语言处理概念的传统策略。因此,随着时间的推移,这些经过试验和测试的方法在各种数据集和问题中被证明是成功的。接下来将是利用深度学习模型对文本数据进行特征工程的详细策略!

要阅读关于连续数值数据的特征工程策略,请查看本系列的第 1 部分的

要了解离散分类数据的特征工程策略,请查看本系列的 第 2 部分

本文中使用的所有代码和数据集都可以从我的 GitHub 中获得

该代码也可作为 Jupyter 笔记本

如果你对我的文章或数据科学有任何反馈、评论或有趣的见解要分享,请随时通过我的 LinkedIn 社交媒体频道联系我。

**** [## Dipanjan Sarkar | LinkedIn

查看 Dipanjan Sarkar 在世界最大的职业社区 LinkedIn 上的个人资料。Dipanjan 有 5 份工作列在…

www.linkedin.com](www.linkedin.com/in/dipanzan…)****

文本数据深度学习方法的直观实践方法— Word2Vec、GloVe 和 FastText

原文:towardsdatascience.com/understandi…

了解特征工程(第四部分)

驯服非结构化文本数据的更新、高级策略

介绍

处理非结构化文本数据非常困难,尤其是当你试图构建一个智能系统,像人类一样解释和理解自由流动的自然语言时。您需要能够处理嘈杂的、非结构化的文本数据,并将其转换为任何机器学习算法都可以理解的结构化、矢量化格式。来自自然语言处理、机器学习或深度学习的原理所有这些都属于人工智能的大伞下,是该行业的有效工具。基于我以前的帖子,这里要记住的重要一点是,任何机器学习算法都是基于统计学、数学和优化的原则。因此,他们还不够聪明,不能以原始的、自然的形式处理文本。我们在 第 3 部分:文本数据的传统方法 中介绍了一些从文本数据中提取有意义特征的传统策略。我鼓励你去看看同样的网站,做一个简短的复习。在本文中,我们将探讨更高级的特征工程策略,这些策略通常利用深度学习模型。更具体地说,我们将涵盖 Word2VecGloVeFastText 型号。

动机

我们已经多次讨论过,包括在我们之前的文章 中的 中,特征工程是创建更好的机器学习模型的秘方。永远记住,即使有了自动化特征工程能力的出现,你仍然需要理解应用这些技术背后的核心概念。否则,它们只是黑盒模型,你不知道如何针对你试图解决的问题进行调整和优化。

传统模式的缺点

用于文本数据的传统(基于计数的)特征工程策略涉及属于通常被称为单词袋模型的模型家族的模型。这包括术语频率、TF-IDF(术语频率-逆文档频率)、N 元语法等等。虽然它们是从文本中提取特征的有效方法,但由于模型的固有性质只是一个非结构化单词的包,我们会丢失每个文本文档中邻近单词周围的附加信息,如语义、结构、序列和上下文。这为我们探索更复杂的模型提供了足够的动力,这些模型可以捕捉这些信息,并为我们提供单词的向量表示特征,通常称为嵌入。

对单词嵌入的需求

虽然这确实有些道理,但是我们为什么要有足够的动力去学习和构建这些单词嵌入呢?关于语音或图像识别系统,所有信息已经以嵌入在高维数据集中的丰富密集特征向量的形式存在,如音频频谱图和图像像素强度。然而,当涉及到原始文本数据时,尤其是像单词包这样基于计数的模型,我们处理的是单个单词,这些单词可能有自己的标识符,并且没有捕获单词之间的语义关系。这导致文本数据的巨大稀疏词向量,因此,如果我们没有足够的数据,我们可能最终会得到糟糕的模型,甚至由于维数灾难而过度拟合数据。

Comparing feature representations for audio, image and text

为了克服基于词袋模型的特征丢失语义和特征稀疏的缺点,我们需要利用【VSMs】向量空间模型,在这个连续的向量空间中基于语义和上下文相似度嵌入词向量。事实上, 分布语义学 领域中的 分布假说 告诉我们,在同一语境中出现和使用的词在语义上彼此相似,具有相似的意义。简单来说,‘一言以蔽之’。其中一篇详细谈论这些语义词向量和各种类型的著名论文是‘不要算,预测!Baroni 等人对上下文计数与上下文预测语义向量的系统比较。我们不会深入探讨,但简而言之,有两种主要的上下文单词向量方法。等基于计数的方法 【潜在语义分析】(LSA)可用于计算单词与其相邻单词在语料库中出现频率的一些统计度量,然后根据这些度量为每个单词构建密集的单词向量。 预测方法基于神经网络的语言模型 尝试通过查看语料库中的单词序列从其相邻单词中预测单词,在此过程中,它学习分布式表示,给我们提供密集的单词嵌入。在本文中,我们将重点关注这些预测方法。

特征工程策略

让我们看看处理文本数据并从中提取有意义的特征的一些高级策略,这些策略可用于下游的机器学习系统。请注意,您可以在我的 GitHub 库中访问本文中使用的所有代码,以供将来参考。我们将从加载一些基本的依赖项和设置开始。

****import pandas as pd
import numpy as np
import re
import nltk
import matplotlib.pyplot as pltpd.options.display.max_colwidth = 200
%matplotlib inline****

我们现在将采用一些文档的语料库,我们将在其上执行所有的分析。对于其中一个语料库,我们将重用我们上一篇文章中的语料库, 第 3 部分:文本数据的传统方法 。为了便于理解,我们将代码描述如下。

Our sample text corpus

我们的玩具语料库由属于几个类别的文档组成。本文中我们将使用的另一个语料库是通过nltk中的corpus模块从 项目中免费获得的 钦定版圣经 。在下一节中,我们将很快加载它。在我们谈论特征工程之前,我们需要预处理和规范化这个文本。

文本预处理

可以有多种清理和预处理文本数据的方式。在自然语言处理(NLP)管道中大量使用的最重要的技术已经在本系列 第 3 部分【文本预处理】 部分中详细强调。由于本文的重点是功能工程,就像我们的上一篇文章一样,我们将重用我们简单的文本预处理程序,它专注于删除文本语料库中的特殊字符、额外空格、数字、停用词和小写字母。

一旦我们准备好了基本的预处理流水线,让我们首先把它应用到我们的玩具语料库中。

***norm_corpus = normalize_corpus(corpus)
norm_corpus**Output
------** array(['sky blue beautiful', 'love blue beautiful sky',
       'quick brown fox jumps lazy dog',
       'kings breakfast sausages ham bacon eggs toast beans',
       'love green eggs ham sausages bacon',
       'brown fox quick blue dog lazy', 
       'sky blue sky beautiful today',
       'dog lazy brown fox quick'],
      dtype='<U51')***

现在让我们使用nltk加载基于 钦定版圣经 的其他语料库,并对文本进行预处理。

下面的输出显示了我们的语料库中的总行数,以及预处理如何对文本内容进行处理。

*****Output
------**Total lines: 30103

Sample line: ['1', ':', '6', 'And', 'God', 'said', ',', 'Let', 'there', 'be', 'a', 'firmament', 'in', 'the', 'midst', 'of', 'the', 'waters', ',', 'and', 'let', 'it', 'divide', 'the', 'waters', 'from', 'the', 'waters', '.']

Processed line: god said let firmament midst waters let divide waters waters***

让我们看看现在流行的一些单词嵌入模型和来自我们语料库的工程特征!

Word2Vec 模型

该模型由谷歌在 2013 年创建,是一种基于预测性深度学习的模型,用于计算和生成高质量、分布式和连续的单词密集矢量表示,这些表示捕捉上下文和语义的相似性。本质上,这些是无监督的模型,可以接受大量文本语料库,创建可能单词的词汇表,并在表示该词汇表的向量空间中为每个单词生成密集的单词嵌入。通常你可以指定单词嵌入向量的大小,向量的总数实质上就是词汇量的大小。这使得这个密集向量空间的维数比使用传统单词袋模型构建的高维稀疏向量空间低得多。

Word2Vec 可以利用两种不同的模型架构来创建这些单词嵌入表示。这些包括:

  • 连续单词包(CBOW)模型
  • 跳格模型

最初由 Mikolov 等人介绍,我建议感兴趣的读者阅读围绕这些模型的原始论文,包括 Mikolov 等人的 【单词和短语的分布式表示及其组合性】和 Mikolov 等人的 【向量空间中单词表示的有效估计】,以获得一些很好的深入观点。

连续词汇袋模型

CBOW 模型架构试图基于源上下文单词(周围单词)来预测当前目标单词(中心单词)。考虑一个简单的句子, “敏捷的棕色狐狸跳过懒惰的狗” ,这可以是成对的 (context_window,target_word) 其中如果我们考虑大小为 2 的上下文窗口,我们有这样的例子: (【敏捷,狐狸】、棕色)、(【the,棕色)、敏捷)、(【the,狗】、 等等。因此,该模型试图基于context_window单词来预测target_word

The CBOW model architecture (Source: arxiv.org/pdf/1301.37… Mikolov el al.)

虽然 Word2Vec 系列模型是无监督的,但这意味着你可以给它一个没有额外标签或信息的语料库,它可以从语料库中构建密集的单词嵌入。但是一旦你有了这个语料库,你仍然需要利用一个监督的分类方法来得到这些嵌入。但是我们将从语料库本身中进行,没有任何辅助信息。我们现在可以将这个 CBOW 架构建模为深度学习分类模型,使得我们接受 上下文单词作为我们的输入,X 并且尝试预测 目标单词,Y 。事实上,构建这种架构比 skip-gram 模型更简单,在 skip-gram 模型中,我们试图从源目标单词预测一整串上下文单词。

实施连续单词袋(CBOW)模型

虽然使用像 gensim 这样的具有 Word2Vec 模型的健壮框架是很好的,但是让我们从头开始尝试实现它,以获得一些关于幕后真正工作方式的观点。我们将利用我们的 圣经语料库 中包含的norm_bible变量来训练我们的模型。实施将集中在四个部分

  • 建立语料库词汇
  • 构建一个 CBOW(上下文,目标)生成器
  • 构建 CBOW 模型架构
  • 训练模型
  • 获取单词嵌入

不要再拖延了,让我们开始吧!

建立语料库词汇

首先,我们将首先构建我们的语料库词汇,从我们的词汇中提取每个唯一的单词,并为其映射一个唯一的数字标识符。

*****Output
------**Vocabulary Size: 12425
Vocabulary Sample: [('perceived', 1460), ('flagon', 7287), ('gardener', 11641), ('named', 973), ('remain', 732), ('sticketh', 10622), ('abstinence', 11848), ('rufus', 8190), ('adversary', 2018), ('jehoiachin', 3189)]***

因此,您可以看到,我们已经在语料库中创建了独特单词的词汇表,以及将单词映射到其独特标识符的方法,反之亦然。如果需要,**PAD**术语通常用于将上下文单词填充到固定长度。

构建一个 CBOW(上下文,目标)生成器

我们需要由目标中心词和周围上下文词组成的词对。在我们的实现中,一个 目标单词 的长度为**1**,而 周围上下文 的长度为**2 x window_size**,其中我们在语料库中的目标单词前后取**window_size** 单词。通过下面的例子,这将变得更加清楚。

*****Context (X): ['old','testament','james','bible'] -> Target (Y): king
Context (X): ['first','book','called','genesis'] -> Target(Y): moses
Context(X):['beginning','god','heaven','earth'] -> Target(Y):created
Context (X):['earth','without','void','darkness'] -> Target(Y): form
Context (X): ['without','form','darkness','upon'] -> Target(Y): void
Context (X): ['form', 'void', 'upon', 'face'] -> Target(Y): darkness
Context (X): ['void', 'darkness', 'face', 'deep'] -> Target(Y): upon
Context (X): ['spirit', 'god', 'upon', 'face'] -> Target (Y): moved
Context (X): ['god', 'moved', 'face', 'waters'] -> Target (Y): upon
Context (X): ['god', 'said', 'light', 'light'] -> Target (Y): let
Context (X): ['god', 'saw', 'good', 'god'] -> Target (Y): light*****

前面的输出应该让您对 X 如何形成我们的上下文单词有了更多的了解,我们正试图根据这个上下文来预测目标中心单词 Y 。举个例子,如果原文是‘太初神造天地’,经过预处理,去掉停用词后就变成了‘太初神 造了 【天地’,对我们来说,我们要达到的是,给定【始、神、天、地】为语境,目标中心词是什么,这种情况下就是' 创造了'**

构建 CBOW 模型架构

我们现在利用tensorflow之上的keras来为 CBOW 模型构建我们的深度学习架构。为此,我们的输入将是传递到嵌入层的上下文单词(用随机权重初始化)。单词嵌入被传播到 lambda 层,在那里我们平均出单词嵌入 (因此称为 CBOW,因为我们在平均时并不真正考虑上下文单词中的顺序或序列) ,然后我们将这个平均的上下文嵌入传递到预测我们的目标单词的密集 softmax 层。我们将其与实际的目标单词进行匹配,通过利用categorical_crossentropy损失来计算损失,并在每个时期执行反向传播,以在该过程中更新嵌入层。下面的代码向我们展示了我们的模型架构。

CBOW model summary and architecture

如果你仍然难以可视化上述深度学习模型,我建议你通读我之前提到的论文。我将尝试用简单的术语来总结这个模型的核心概念。我们有尺寸为**(2 x window_size)**输入上下文单词 ,我们将把它们传递给尺寸为**(vocab_size x embed_size)**嵌入层 ,这将为这些上下文单词**(1 x embed_size for each word)**中的每一个给出 密集单词嵌入 。接下来,我们使用一个λ层*** 来平均这些嵌入,并得到一个 平均密集嵌入 **(1 x embed_size)**,它被发送到 密集 softmax 层 ,后者输出最可能的目标单词。我们将其与实际的目标单词进行比较,计算损失,反向传播误差以调整权重(在嵌入层中),并对多个时期的所有*(上下文,目标)对重复该过程。下图试图解释同样的情况。**

Visual depiction of the CBOW deep learning model

我们现在准备在我们的语料库上训练这个模型,使用我们的数据生成器输入 (上下文,目标单词) 对。

训练模型

在我们完整的语料库上运行模型需要相当多的时间,所以我只运行了 5 个时期。您可以利用下面的代码,并在必要时增加更多的历元。

***Epoch: 1 	Loss: 4257900.60084
Epoch: 2 	Loss: 4256209.59646
Epoch: 3 	Loss: 4247990.90456
Epoch: 4 	Loss: 4225663.18927
Epoch: 5 	Loss: 4104501.48929***

****注意:运行这个模型计算量很大,如果使用 GPU 进行训练,效果会更好。我在一个 AWS **p2.x**实例上用 Tesla K80 GPU 对其进行了训练,仅 5 个纪元就花了我将近 1.5 小时!

一旦这个模型被训练,相似的单词应该基于嵌入层具有相似的权重,并且我们可以测试出相同的权重。

获取单词嵌入

为了获得整个词汇表的单词嵌入,我们可以利用下面的代码从嵌入层中提取出相同的内容。我们不在位置 0 进行嵌入,因为它属于填充项**(PAD)**,而填充项并不是真正感兴趣的单词。

Word Embeddings for our vocabulary based on the CBOW model

因此,您可以清楚地看到,每个单词都有一个大小为**(1x100)**的密集嵌入,如前面的输出所示。让我们试着根据这些嵌入,为感兴趣的特定单词找出一些上下文相似的单词。为此,我们基于密集嵌入向量在我们的词汇表中的所有单词之间建立成对距离矩阵,然后基于最短(欧几里德)距离找出每个感兴趣单词的 n 个最近邻居。

*****(12424, 12424)****{'egypt': ['destroy', 'none', 'whole', 'jacob', 'sea'],
 'famine': ['wickedness', 'sore', 'countries', 'cease', 'portion'],
 'god': ['therefore', 'heard', 'may', 'behold', 'heaven'],
 'gospel': ['church', 'fowls', 'churches', 'preached', 'doctrine'],
 'jesus': ['law', 'heard', 'world', 'many', 'dead'],
 'john': ['dream', 'bones', 'held', 'present', 'alive'],
 'moses': ['pharaoh', 'gate', 'jews', 'departed', 'lifted'],
 'noah': ['abram', 'plagues', 'hananiah', 'korah', 'sarah']}*****

你可以清楚地看到,其中一些在上下文中有意义 【上帝,天堂】【福音,教会】*** 等等,而一些可能没有意义。训练更多的纪元通常会得到更好的结果。现在,我们将探讨 skip-gram 体系结构,与 CBOW 相比,它通常会给出更好的结果。***

跳格模型

跳格模型体系结构通常试图实现 CBOW 模型的反向操作。它试图在给定目标单词(中心单词)的情况下预测源上下文单词(周围的单词)。考虑到我们前面的简单句子, “敏捷的棕色狐狸跳过懒惰的狗”。 如果我们使用 CBOW 模型,我们得到成对的 (context_window,target_word) 其中如果我们考虑大小为 2 的上下文窗口,我们有类似于 ([quick,fox],brown),([the,brown),quick),([the,dog],lazy) 等例子。现在考虑到 skip-gram 模型的目的是从目标单词预测上下文,该模型通常反转上下文和目标,并试图从其目标单词预测每个上下文单词。于是任务就变成了预测上下文 【快,狐狸】 给定目标词 【布朗】【the,brown】给定目标词 【快】 等等。因此,该模型试图基于目标单词来预测上下文窗口单词。

The Skip-gram model architecture (Source: arxiv.org/pdf/1301.37… Mikolov el al.)

就像我们在 CBOW 模型中讨论的那样,我们现在需要将这种跳过语法的架构建模为深度学习分类模型,以便我们将目标单词作为我们的输入,并尝试预测上下文单词。这变得有点复杂,因为在我们的上下文中有多个单词。我们通过将每个 (目标,上下文 _ 单词)对 分解成 (目标,上下文)对 来进一步简化这一点,使得每个上下文仅由一个单词组成。因此,我们之前的数据集被转换成像 (棕色,快速),(棕色,狐狸),(快速,the),(快速,棕色) 这样的对。但是如何监督或训练模型知道什么是上下文相关的,什么不是?**

为此,我们喂我们的跳过图模型对 (X,Y) 其中 X 是我们的 输入Y是我们的 标签 。我们通过使用 [(目标,上下文),1] 对作为 实际输入样本 来做到这一点,其中 目标 是我们感兴趣的单词,而 上下文 是出现在目标单词附近的上下文单词,并且 正标签 1 指示这是上下文相关的对。我们还输入 [(目标,随机),0] 对作为 负输入样本 其中 目标 再次是我们感兴趣的单词,但是 随机 只是从我们的词汇表中随机选择的单词,与我们的目标单词没有上下文或关联。因此 否定标记 0 表示这是一个上下文不相关的对。我们这样做是为了让模型能够学习哪些词对是上下文相关的,哪些是不相关的,并为语义相似的词生成相似的嵌入。

实现跳格模型

现在,让我们从头开始尝试并实现这个模型,以获得一些关于幕后工作方式的视角,这样我们就可以将其与 CBOW 模型的实现进行比较。我们将像往常一样利用包含在**norm_bible**变量中的圣经语料库来训练我们的模型。实施将集中在五个部分

  • 建立语料库词汇
  • 构建一个 skip-gram [(目标,上下文),关联]生成器
  • 构建跳格模型架构
  • 训练模型
  • 获取单词嵌入

让我们开始构建我们的 skip-gram Word2Vec 模型吧!

建立语料库词汇

首先,我们将遵循构建语料库词汇的标准流程,从我们的词汇中提取每个唯一的单词,并分配一个唯一的标识符,类似于我们在 CBOW 模型中所做的。我们还维护将单词转换成它们的唯一标识符的映射,反之亦然。

***Vocabulary Size: 12425
Vocabulary Sample: [('perceived', 1460), ('flagon', 7287), ('gardener', 11641), ('named', 973), ('remain', 732), ('sticketh', 10622), ('abstinence', 11848), ('rufus', 8190), ('adversary', 2018), ('jehoiachin', 3189)]***

正如我们所希望的那样,语料库中的每个独特的单词现在都是我们词汇表的一部分,带有一个独特的数字标识符。

构建一个 skip-gram [(目标,上下文),相关性]生成器

现在是时候构建我们的跳格生成器了,它会像我们之前讨论的那样给我们一对单词和它们的相关性。幸运的是,keras有一个可以使用的漂亮的skipgrams实用程序,我们不必像在 CBOW 中那样手动实现这个生成器。

****注:功能[**skipgrams(…)**](https://keras.io/preprocessing/sequence/#skipgrams)出现在[**keras.preprocessing.sequence**](https://keras.io/preprocessing/sequence)

此函数将一系列单词索引(整数列表)转换为以下形式的单词元组:

-(字,字在同一个窗口),标签为 1(阳性样本)。

-(单词,从词汇表中随机抽取的单词),标签为 0(负样本)。

*****(james (1154), king (13)) -> 1
(king (13), james (1154)) -> 1
(james (1154), perform (1249)) -> 0
(bible (5766), dismissed (6274)) -> 0
(king (13), alter (5275)) -> 0
(james (1154), bible (5766)) -> 1
(king (13), bible (5766)) -> 1
(bible (5766), king (13)) -> 1
(king (13), compassion (1279)) -> 0
(james (1154), foreskins (4844)) -> 0*****

因此,您可以看到我们已经成功地生成了所需的跳转图,并且基于前面输出中的示例跳转图,您可以根据标签(0 或 1)清楚地看到什么是相关的,什么是不相关的。

构建跳格模型架构

我们现在在tensorflow的基础上利用keras来为跳格模型构建我们的深度学习架构。为此,我们的输入将是我们的目标单词和上下文或随机单词对。每一个都被传递给它自己的嵌入层(用随机权重初始化)。一旦我们获得了目标和上下文单词的单词嵌入,我们就把它传递给一个合并层,在那里我们计算这两个向量的点积。然后,我们将这个点积值传递给密集的 sigmoid 层,该层根据这对单词是上下文相关的还是只是随机单词来预测 1 或 0(Y’)。我们将其与实际的相关性标签( Y )进行匹配,通过利用mean_squared_error损失来计算损失,并在该过程中利用每个时期执行反向传播来更新嵌入层。下面的代码向我们展示了我们的模型架构。

Skip-gram model summary and architecture

理解上面的深度学习模型非常简单。不过,为了便于理解,我会尽量用简单的词语来概括这个模型的核心概念。对于每个训练示例,我们有一对输入单词,由具有唯一数字标识符的一个输入目标单词 和具有唯一数字标识符的一个上下文单词 组成。如果是 一个正例 这个词有上下文意义,是 一个上下文词 和我们的 标注 Y=1 ,否则如果是 一个负例 ,这个词没有上下文意义,只是 一个随机词 和我们的 标注 Y=0 我们将把它们中的每一个传递给它们自己的 嵌入层 ,大小为**(vocab_size x embed_size)**,这将为这两个单词**(1 x embed_size for each word)**中的每一个给我们 密集单词嵌入 。接下来我们使用一个 合并层 来计算这两个嵌入的 点积 并得到点积值。这然后被发送到 密集 s 形层 ,其输出 1 或 0。我们将其与实际标签 Y (1 或 0)进行比较,计算损失,反向传播误差以调整权重(在嵌入层中),并对多个时期的所有 【目标,上下文】 对重复该过程。下图试图解释同样的情况。

Visual depiction of the Skip-gram deep learning model

现在让我们开始用跳步图训练我们的模型。

训练模型

在我们的完整语料库上运行该模型需要相当多的时间,但比 CBOW 模型少。因此,我只运行了 5 个时期。您可以利用下面的代码,并在必要时增加更多的历元。

Epoch: 1 Loss: 4529.63803683
Epoch: 2 Loss: 3750.71884749
Epoch: 3 Loss: 3752.47489296
Epoch: 4 Loss: 3793.9177565
Epoch: 5 Loss: 3716.07605051

一旦这个模型被训练,相似的单词应该基于嵌入层具有相似的权重,并且我们可以测试出相同的权重。

获取单词嵌入

为了获得整个词汇表的单词嵌入,我们可以利用下面的代码从嵌入层中提取出相同的内容。请注意,我们只对目标单词嵌入层感兴趣,因此我们将从我们的**word_model**嵌入层中提取嵌入。我们不在位置 0 进行嵌入,因为词汇表中没有一个单词的数字标识符为 0,我们忽略它。

Word Embeddings for our vocabulary based on the Skip-gram model

因此,您可以清楚地看到,每个单词都有一个大小为**(1x100)**的密集嵌入,如前面的输出所示,类似于我们从 CBOW 模型中获得的结果。现在让我们对这些密集的嵌入向量应用欧几里德距离度量来为我们的词汇表中的每个单词生成成对的距离度量。然后,我们可以基于最短(欧几里德)距离找出每个感兴趣单词的 n 个最近邻,类似于我们在 CBOW 模型的嵌入中所做的。

(**12424, 12424)****{'egypt': ['pharaoh', 'mighty', 'houses', 'kept', 'possess'],
 'famine': ['rivers', 'foot', 'pestilence', 'wash', 'sabbaths'],
 'god': ['evil', 'iniquity', 'none', 'mighty', 'mercy'],
 'gospel': ['grace', 'shame', 'believed', 'verily', 'everlasting'],
 'jesus': ['christ', 'faith', 'disciples', 'dead', 'say'],
 'john': ['ghost', 'knew', 'peter', 'alone', 'master'],
 'moses': ['commanded', 'offerings', 'kept', 'presence', 'lamb'],
 'noah': ['flood', 'shem', 'peleg', 'abram', 'chose']}**

从结果中可以清楚地看到,对于每个感兴趣的单词,许多相似的单词是有意义的,并且我们已经获得了比 CBOW 模型更好的结果。让我们现在使用 t-SNE 来可视化这些单词嵌入,T11 代表t-分布式随机邻居嵌入 一种流行的降维技术来在较低维(例如 2-D)中可视化高维空间。

Visualizing skip-gram word2vec word embeddings using t-SNE

我用红色标记了一些圆圈,这些圆圈似乎显示了在向量空间中位置相近的上下文相似的不同单词。如果你发现任何其他有趣的模式,随时让我知道!

基于 Gensim 的鲁棒 Word2Vec 模型

虽然我们的实现足够好,但它们还没有优化到能在大型语料库上很好地工作。由雷迪姆·řehůřek 创建的[**gensim**](https://radimrehurek.com/gensim/)框架由 Word2Vec 模型的健壮、高效和可伸缩的实现组成。我们将在圣经语料库中利用同样的方法。在我们的工作流程中,我们将标记化我们的规范化语料库,然后关注 Word2Vec 模型中的以下四个参数来构建它。

  • **size** : 一词嵌入维度
  • **window** : 上下文窗口大小
  • **min_count** : 最小字数
  • **sample** : 常用词的下采样设置

在建立我们的模型之后,我们将使用我们感兴趣的单词来查看每个单词的顶部相似单词。

这里的相似词肯定与我们感兴趣的词更相关,这是预料之中的,因为我们对这个模型进行了更多次迭代,这一定产生了更好和更多的上下文嵌入。你注意到什么有趣的联想了吗?

Noah’s sons come up as the most contextually similar entities from our model!

在使用 t-SNE 将它们的维度减少到 2-D 空间之后,让我们也使用它们的嵌入向量来可视化感兴趣的单词和它们的相似单词。

Visualizing our word2vec word embeddings using t-SNE

我画的红圈指出了我发现的一些有趣的联想。根据我之前的描述,我们可以清楚地看到 诺亚 和他的儿子们根据我们模型中的单词 embeddings 彼此非常接近!

将 Word2Vec 特性应用于机器学习任务

如果你还记得阅读过上一篇文章 第三部分:文本数据的传统方法 你可能已经看到过我使用特性来完成一些实际的机器学习任务,比如聚类。让我们利用我们的其他顶级语料库,并尝试实现同样的目标。首先,我们将在语料库上构建一个简单的 Word2Vec 模型,并可视化嵌入。

Visualizing word2vec word embeddings on our toy corpus

请记住,我们的语料库非常小,因此要获得有意义的单词嵌入,并让模型获得更多的上下文和语义,更多的数据是有帮助的。那么在这个场景中嵌入的单词是什么呢?它通常是每个单词的密集向量,如以下单词 sky 的示例所示。

w2v_model.wv['sky']**Output
------**array([ 0.04576328,  0.02328374, -0.04483001,  0.0086611 ,  0.05173225, 0.00953358, -0.04087641, -0.00427487, -0.0456274 ,  0.02155695], dtype=float32)

现在,假设我们想从我们的玩具语料库中聚集八个文档,我们需要从每个文档中出现的每个单词中获得文档级嵌入。一种策略是平均文档中每个单词的单词嵌入量。这是一个非常有用的策略,你可以用它来解决你自己的问题。现在让我们将它应用到我们的语料库中,以获得每个文档的特征。

Document level embeddings

现在我们有了每个文档的特征,让我们使用 相似性传播 算法对这些文档进行聚类,这是一种基于数据点之间的*“消息传递”*概念的聚类算法,并且不需要将聚类的数量作为显式输入,而这是基于分区的聚类算法经常需要的。

Clusters assigned based on our document features from word2vec

我们可以看到,我们的算法已经根据 Word2Vec 特性将每个文档聚类到正确的组中。相当整洁!我们还可以通过使用 主成分分析(PCA) 来将特征维度降低到二维,然后将其可视化(通过对每个聚类进行颜色编码),来可视化每个文档在每个聚类中的位置。

Visualizing our document clusters

每一个簇中的文档看起来都是有序的,因为每个簇中的文档彼此靠近,而与其他簇相距较远。

手套模型

GloVe 模型代表全局向量,这是一种无监督的学习模型,可用于获得类似于 Word2Vec 的密集单词向量。然而,该技术是不同的,并且在聚集的全局单词-单词共现矩阵上执行训练,给我们一个具有有意义的子结构的向量空间。这种方法是由 Pennington 等人在斯坦福发明的,我推荐你阅读关于 GloVe 的原始论文,*‘GloVe:Global Vectors for Word Representation’*Pennington 等人的文章,这是一篇很好的阅读材料,可以让你对这个模型的工作原理有一些了解。

我们不会在这里过多地讨论该模型的实现细节,但是如果你对实际代码感兴趣,你可以查看官方的 页面 。我们将保持事情简单,并试图理解手套模型背后的基本概念。我们已经讨论了基于计数的矩阵分解方法,如 LSA 和预测方法,如 Word2Vec。这篇论文声称,目前,两个家庭都有明显的缺陷。像 LSA 这样的方法有效地利用了统计信息,但它们在单词类比任务上表现相对较差,比如我们如何找出语义相似的单词。像 skip-gram 这样的方法可能在类比任务上做得更好,但是它们在全局水平上很少利用语料库的统计数据。

GloVe 模型的基本方法是首先创建一个由(单词,上下文)对组成的巨大的单词-上下文共现矩阵,使得该矩阵中的每个元素表示单词与上下文(可以是单词序列)一起出现的频率。想法是应用矩阵分解来近似这个矩阵,如下图所示。

Conceptual model for the GloVe model’s implementation

考虑到***【WC】矩阵、 词-特征(WF) 矩阵和 特征-上下文(FC) 矩阵,我们尝试对**WC = WF x FC**进行因式分解,这样我们的目标是从FC 中重构 WC 为此,我们通常用一些随机权重初始化*【WF】FC ,并尝试将它们相乘以获得【WC’**(WC 的近似值),并测量它与 WC 的接近程度。我们多次使用【SGD】来最小化误差。最后, 单词特征矩阵(WF) 给出了每个单词的单词嵌入,其中 F 可以预设为特定的维数。需要记住的非常重要的一点是,Word2Vec 和 GloVe 模型在工作方式上非常相似。它们都旨在建立一个向量空间,其中每个单词的位置都受其相邻单词基于上下文和语义的影响。Word2Vec 从单词共现对的局部单个示例开始,GloVe 从语料库中所有单词的全局聚合共现统计开始。

将手套特征应用于机器学习任务

让我们尝试利用基于手套的嵌入来完成我们的文档聚类任务。非常流行的[**spacy**](https://spacy.io)框架具有基于不同语言模型利用手套嵌入的能力。你也可以获得预先训练好的单词向量,并根据需要使用gensimspacy加载它们。我们将首先安装 spacy 并使用 en_vectors_web_lg 模型,该模型由在普通爬行上训练的 300 维单词向量组成。

*# Use the following command to install spaCy
> pip install -U spacyOR> conda install -c conda-forge spacy# Download the following language model and store it in disk
[https://github.com/explosion/spacy-models/releases/tag/en_vectors_web_lg-2.0.0](https://github.com/explosion/spacy-models/releases/tag/en_vectors_web_lg-2.0.0)# Link the same to spacy 
> python -m spacy link ./spacymodels/en_vectors_web_lg-2.0.0/en_vectors_web_lg en_vecsLinking successful
    ./spacymodels/en_vectors_web_lg-2.0.0/en_vectors_web_lg --> ./Anaconda3/lib/site-packages/spacy/data/en_vecsYou can now load the model via spacy.load('en_vecs')*

spacy中也有自动安装模型的方法,如果需要,你可以查看他们的模型&语言页面了解更多信息。我有一些同样的问题,所以我不得不手动加载它们。我们现在将使用spacy加载我们的语言模型。

***Total word vectors: 1070971***

这验证了一切都在正常工作。现在,让我们在玩具语料库中获取每个单词的手套嵌入。

GloVe embeddings for words in our toy corpus

我们现在可以使用 t-SNE 来可视化这些嵌入,类似于我们使用 Word2Vec 嵌入所做的。

Visualizing GloVe word embeddings on our toy corpus

spacy的美妙之处在于,它会自动为你提供每个文档中单词的平均嵌入量,而不必像我们在 Word2Vec 中那样实现一个功能。我们将利用同样的方法为我们的语料库获取文档特征,并使用 k-means 聚类来对我们的文档进行聚类。

Clusters assigned based on our document features from GloVe

我们看到一致的集群,类似于我们从 Word2Vec 模型中获得的集群,这很好!GloVe 模型声称在许多情况下比 Word2Vec 模型表现更好,如下图所示,该图来自 Pennington 等人的原始论文。

GloVe vs Word2Vec performance (Source: nlp.stanford.edu/pubs/glove.… by Pennington et al.)

上述实验是通过在相同的 6B 令牌语料库(Wikipedia 2014 + Gigaword 5)上训练 300 维向量来完成的,该语料库具有相同的 40 万单词词汇和大小为 10 的对称上下文窗口,以防有人对细节感兴趣。

快速文本模型

脸书在 2016 年首次推出了 FastText 模型,作为普通 Word2Vec 模型的扩展和据称的改进。基于 Mikolov 等人的题为 “用子词信息丰富词向量”的原始论文,这是深入理解该模型如何工作的一篇好文章。总的来说,FastText 是一个用于学习单词表示以及执行健壮、快速和准确的文本分类的框架。该框架由脸书GitHub 上开源,并声称有如下内容。

虽然我还没有从零开始实现这个模型,但根据这篇研究论文,以下是我对这个模型如何工作的了解。一般而言,像 Word2Vec 模型这样的预测模型通常将每个单词视为不同的实体(例如 ,其中 ),并为该单词生成密集嵌入。然而,这对于具有大量词汇和许多在不同语料库中不经常出现的罕见单词的语言来说是一个严重的限制。Word2Vec 模型通常忽略每个单词的形态结构,并将单词视为单个实体。FastText 模型将每个单词视为一包字符 n 元语法。这在文中也称为子字模型。

我们在单词的开头和结尾加上特殊的边界符号 <> 。这使我们能够将前缀和后缀与其他字符序列区分开来。我们还将单词 w 本身包含在其 n-grams 的集合中,以学习每个单词的表示(除了它的字符 n-grams)。以单词 其中n = 3*(tri-grams)为例,将由字符 n-grams: < wh、whe、her、ere、re > 和特殊序列 <其中> 表示整个单词。注意顺序,对应字 <她> 不同于三字组 来自字 其中 。*

在实践中,本文推荐在抽取所有 n 元文法时,对于n≥3n≤**6。这是一种非常简单的方法,可以考虑不同组的 n 元语法,例如取所有的前缀和后缀。我们通常将一个单词的向量表示(嵌入)与每个 n 元语法相关联。因此,我们可以用一个单词的 n 元文法的向量表示的和或者这些 n 元文法的嵌入的平均值来表示这个单词。因此,由于这种基于单词的字符利用单个单词的 n 元语法的效果,稀有单词有更高的机会获得良好的表示,因为它们的基于字符的 n 元语法应该出现在语料库的其他单词中。

将快速文本特征应用于机器学习任务

gensim包有很好的包装器,提供用户界面来利用gensim.models.fasttext模块下可用的 FastText 模型。让我们在圣经语料库 上再次应用这一点,看看我们感兴趣的单词和它们最相似的单词。

你可以在我们的 Word2Vec 模型的结果中看到很多相似之处,每个感兴趣的单词都有相关的相似单词。你注意到什么有趣的联系和相似之处了吗?

Moses, his brother Aaron and the Tabernacle of Moses

***注意:*运行该模型在计算上是昂贵的,并且与 skip-gram 模型相比通常花费更多的时间,因为它为每个单词考虑 n-grams。如果使用 GPU 或良好的 CPU 进行训练,效果会更好。我在 AWS ***p2.x***实例上对此进行了训练,花费了我大约 10 分钟的时间,相比之下,在常规系统上花费了 2-3 个小时。

现在让我们使用 主成分分析(PCA) 将单词嵌入维数降低到 2-D,然后将其可视化。

Visualizing FastTest word embeddings on our Bible corpus

我们可以看到很多有趣的图案!诺亚,他的儿子和爷爷玛士撒拉关系亲密。我们也看到摩西埃及联系在一起,在那里它忍受了圣经中的瘟疫,包括饥荒瘟疫*。同样耶稣和他的一些门徒彼此关系密切。*

要访问任何单词嵌入,您可以用如下单词索引模型。

***ft_model.wv['jesus']**array([-0.23493268,  0.14237943,  0.35635167,  0.34680951,    
        0.09342121,..., -0.15021783, -0.08518736, -0.28278247,   
       -0.19060139], dtype=float32)*

有了这些嵌入,我们可以执行一些有趣的自然语言任务。其中之一是找出不同单词(实体)之间的相似性。

*print(ft_model.wv.similarity(w1='god', w2='satan'))
print(ft_model.wv.similarity(w1='god', w2='jesus'))**Output
------**
0.333260876685
0.698824900473*

我们可以看到 【上帝】【耶稣】 更紧密地联系在一起,而不是基于我们圣经语料库中的文本 【撒旦】 。挺贴切的!

考虑到单词嵌入的存在,我们甚至可以从一堆单词中找出奇怪的单词,如下所示。

*st1 = "god jesus satan john"
print('Odd one out for [',st1, ']:',  
      ft_model.wv.doesnt_match(st1.split()))st2 = "john peter james judas"
print('Odd one out for [',st2, ']:', 
      ft_model.wv.doesnt_match(st2.split()))**Output
------**
**Odd one out for [ god jesus satan john ]: satan
Odd one out for [ john peter james judas ]: judas***

有趣的和相关的结果,在这两种情况下,对于其他单词中的奇数实体!

结论

这些例子应该让你对利用深度学习语言模型从文本数据中提取特征以及解决单词语义、上下文和数据稀疏性等问题的更新和有效的策略有一个很好的想法。接下来将详细介绍利用深度学习模型对图像数据进行特征工程的策略。敬请期待!

要了解连续数值数据的特征工程策略,请查看本系列的第 1 部分

要了解离散分类数据的特征工程策略,请查看本系列的第 2 部分****

要了解针对非结构化文本数据的传统特征工程策略,请查看本系列的第 3 部分****

本文中使用的所有代码和数据集都可以从我的 GitHub 中访问

该代码也可作为 Jupyter 笔记本

除非明确引用,否则架构图是我的版权。你可以随意使用它们,但是如果你想在自己的作品中使用它们,请记得注明出处。

如果你对我的文章或数据科学有任何反馈、评论或有趣的见解要分享,请随时通过我的 LinkedIn 社交媒体频道联系我。

***** [## Dipanjan Sarkar -数据科学家-英特尔公司| LinkedIn

查看 Dipanjan Sarkar 在世界最大的职业社区 LinkedIn 上的个人资料。Dipanjan 有 5 份工作列在…

www.linkedin.com](www.linkedin.com/in/dipanzan…)*****