解析 XML,一次性识别命名实体
Photo credit: Lynda.com
条件随机场,序列预测,序列标记
解析 XML 是一个旨在读取 XML 并为程序使用 XML 创造一种方式的过程。XML 解析器是一种软件,它读取 XML 文件,并将这些文件中的信息提供给应用程序。
在读取 XML 文件时,解析器会检查格式的语法,并报告任何违规情况。
我们今天的主要目标不是解析 XML,而是命名实体识别(NER),但是我们要使用的数据是以 XML 格式存储的。
NER 的任务是确定文本中提到的实体的身份。例如,给定句子“巴黎是法国的首都”,思路是确定“巴黎”指的是巴黎的城市,而不是巴黎希尔顿。
“巴黎是法国的首都”这句话暗示巴黎是一个国家的首都,暗示巴黎是一个城市,而不是一个人的名字。
命名文档集合中发现的实体的任务极具挑战性。幸运的是,UPB数据科学小组从德国提供了一组用于 NLP 交换格式的命名实体识别的带注释的训练数据集,我们将使用其中一个名为 500newsgoldstandard.xml 的数据集,可以在这里找到。
数据
为了研究数据,我们使用prettify()
以分层格式的嵌套数据结构获取文档:
with codecs.open("500newsgoldstandard.xml", "r", "utf-8") as file:
soup = BeautifulSoup(file, "html.parser")
print(soup.prettify())
数据创建者侧重于识别三类主要的命名实体:人、地点和组织。从第一份文件中我们可以看到:
Figure 1
该文件有一句话“美国专利局允许基因获得专利,只要有人通过从细胞中取出 DNA 来分离 DNA,美国公民自由联盟的律师桑德拉·帕克说”,其中“美国公民自由联盟”(一个组织)和“桑德拉·帕克”(一个人的名字)被标记为命名实体。因为它们在namedentityintext
标签之间。
数据预处理
文本预处理包括遍历textwithnamedentities
下元素的每个子元素,否则我们将“N”标记为命名实体“C”的一部分。
以下代码返回文档列表,每个文档都包含单词和标签对。
docs = []
for elem in soup.find_all("document"):
texts = []
for child in elem.find("textwithnamedentities").children:
if type(child) == Tag:
if child.name == "namedentityintext":
label = 'N'
else:
label = 'C'
for w in child.text.split(" "):
if len(w) > 0:
texts.append((w, label))
docs.append(texts)
我们可以再次调查第一份文件。
docs[0]
Figure 2
位置标签
我们将对文档列表应用单词标记和词性标注。
data = []
for i, doc in enumerate(docs):
tokens = [t for t, label in doc]
tagged = pos_tag(tokens)
data.append([(w, pos, label) for (w, label), (word, pos) in zip(doc, tagged)])
这为我们提供了包含单个单词、POS 标签及其标签的元组列表。
data[0]
Figure 3
条件随机场
在命名实体识别领域,我们的输入数据是连续的,我们预测相互依赖以及依赖于其他观察变量的变量,也就是说,我们在对数据点进行预测时考虑周围的上下文,再想想“巴黎是法国的首都”与“巴黎希尔顿”。
所以我们会定义一些特征,比如词的身份、词的部分、下/标题/上标志、词的后缀、词的形状、词的词性标签;此外,使用来自邻近单词的一些信息,以及不在文档开头的单词的所有这些特征,不在文档结尾的单词的所有这些特征。
条件随机场 Python 库,Python-Crfsuite
Python-crfsuite 是一个绑定到 CRFsuite 的 Python,这是一个用于标记顺序数据的条件随机字段(CRF)的实现。该库已被广泛用于命名实体识别。
以下代码大部分摘自 python-crfsuite ,用于上述特征提取:
features_crfsuite.py
训练模型
train_crfModel.py
评估结果
evaluate_results.py
Figure 4
对于第一次尝试来说还不算太差!
Jupyter 笔记本可以在 Github 上找到。享受这周剩下的时光。
参考资料:
[## 条件随机场简介
假设你有一系列贾斯汀比伯生活中某一天的快照,你想给每张照片贴上…
blog.echen.me](http://blog.echen.me/2012/01/03/introduction-to-conditional-random-fields/) [## 在 Python 中使用 CRF 执行序列标记
在自然语言处理中,从给定的文本中抽取特定类型的词或短语是一项常见的任务
www.albertauyeung.com](http://www.albertauyeung.com/post/python-sequence-labelling-with-crf/)
第 1 部分:从头开始的神经网络—基础
在这一系列文章中,我将解释神经网络的内部工作原理。我将为其背后的理论奠定基础,并展示如何用几行简单易懂的 Java 代码编写一个合格的神经网络。
这是系列文章的第一部分:
- 第一部分:基础。
- 第二部分:梯度下降和反向传播。
- 第 3 部分:用 Java 实现。
- 第四部分:更好、更快、更强。
- 第 5 部分:训练网络阅读手写数字。
- 额外 1:我如何通过数据扩充提高 1%的准确度。
- 号外 2:MNIST 游乐场。
背景
几周前,我决定学习机器学习。我最感兴趣的是应用机器学习以及这种范式可能带来的商业机会和新软件。我认为一个合理的前进方式是选择任何框架,如 TensorFlow 或 DL4J 并开始尝试。所以我做了…并且变得沮丧。原因是,仅仅在这些框架中的任何一个中建立一个良好行为的神经网络就需要对概念和内部工作有相当多的理解:*激活函数、优化器、正则化、退出、学习速率退火、*等。——我分明是在黑暗中摸索。
我只是需要对这一切有更深的理解。因此,我一头扎进了浩瀚的互联网信息海洋,经过一周的阅读,我不幸地意识到,没有多少信息转化为知识。我不得不重新思考。
长话短说:我决定自己建立一个小型神经网络。作为一个获取知识的游乐场。
结果很好。从零开始构建时所需的实践/理论组合正是我加深理解的正确方式。这是我学到的。
神经网络
从外部来看,神经网络只是一个函数。因此,它可以接受输入并产生输出。这个函数是高度参数化的,这是非常重要的。
有些参数是我们自己设定的。这些参数被称为超参数,可以被视为我们的神经网络的配置。然而,大多数参数是网络固有的,我们无法直接控制。为了理解为什么这些是重要的,我们需要看看它的内部。
一个简单的神经网络通常由一组隐藏层组成,每个隐藏层包含许多神经元,在图中标记为黄色。输入图层标记为蓝色。
在上面的配置中,输入是大小为 4 的向量 X,输出是大小为 3 的向量 Y。
如图所示,一层中的每个神经元与下一层中的每个神经元之间都有联系。每个这样的连接实际上都是一个参数或权重。在这个例子中,我们已经有了 94 个权重形式的额外参数。在更大的网络中,数量可能更多。这些权重将定义网络的行为方式以及将输入转换为期望输出的能力。
神经元
在我解释如何将网络作为一个整体来转换数据之前,我们需要进一步放大。向单个神经元问好:
每个神经元的输入是前一层中每个神经元输出的加权和。在示例中,这将是:
在此基础上,我们添加了一个称为 bias 的标量值, b ,它给出了神经元的总输入:
然后,输入信号在神经元内通过应用称为激活函数的东西进行转换,表示为 σ 。激活函数的名称源于这样一个事实,即如果输入信号 z 足够大,该函数通常被设计为让信号通过神经元,但是如果 z 不够大,则限制神经元的输出。我们可以认为这是神经元触发或活跃,如果刺激足够强的话。
更重要的是,激活功能增加了网络的非线性,这在试图有效地拟合网络时很重要(通过拟合网络我的意思是训练网络产生我们想要的输出)。没有它,网络将只是其输入的线性组合。
经常使用被称为整流线性单元或 ReLU 的激活函数(或其变体)。ReLU 很简单:
另一种常见的激活功能是这种逻辑功能,称为 sigmoid 功能:
正如你从图中看到的,它们都以我描述的方式运行:如果足够大,它们让信号通过,如果不够大,则限制信号通过。
最后,在将激活函数应用于 z,得到σ(z)之后,我们得到了神经元的输出。
或者以向量形式陈述:在将激活函数应用于向量 z 之后,得到 σ(z) (其中函数被应用于向量 z 中的每个元素)我们从得到该层中所有神经元的输出。
正向输送
现在我们有了描述如何将整个神经网络(即“函数”)应用于一些数据*以获得输出 y = f(x) 的所有零碎信息。*
我们只是通过网络中的每一层传送数据。这叫做进给* 前进它的工作原理是这样的:*
输入 x 并从第一个隐藏层开始:
- *对于当前层 n 中的每个神经元,取前一层 **n - 1 中每个相连神经元输出的加权和。*添加偏差并应用激活功能。
- 进行到层 n + 1 但是现在使用层 n 的输出值作为输入。
- 现在一层一层地进行,直到你到达最后一层。这些神经元的输出将给出 y 。
让我们看一个简单的例子:
假设我们已经选择 sigmoid 函数作为所有层中的激活:
现在让我们一层一层地,一个神经元一个神经元地,计算这个网络在输入向量*×x=【2 ^ 3】*上会给出什么输出。
因此,这个网络在输入 x = [2 3] 上产生输出
y =[0.7122574322957417 0.5330975738715015】。
如果我们幸运,或者擅长设置初始权重和偏差,这可能正是我们想要的输入输出。更有可能的是根本不是我们想要的。如果是后者,我们可以调整权重和偏差,直到得到我们想要的输出。
让我们思考一下,为什么神经网络是这样设计的,为什么调整权重和偏差可能是使网络的行为更符合我们的预期所需要的。
神经网络的表达能力
网络的高度参数化使得它非常能够模仿几乎任何函数。如果我们试图模拟的函数比通过一组权重和偏差可能表达的更复杂,我们可以创建一个稍微大一点的网络(更深和/或更宽*),这将为我们提供更多的参数,从而更好地将网络与我们想要的函数相匹配。*
还要注意,通过构造神经网络的方式,我们可以自由选择输入的任何维度和输出的任何维度。通常,神经网络被设计为降低维度——即将高维空间中的点映射到低维空间中的点。这是典型的用于分类的数据。在本文的最后,我会用一个例子来说明这个问题。
现在考虑在每个神经元中发生了什么:∑(Wx+b)——即,我们将来自前一层的信号馈送给激活函数,但是我们缩放并且首先翻译它。那么,把一个论点转化成一个函数,意味着什么呢?考虑一会儿吧。
为了简单起见,让我们看看当我们缩放函数的输入时,在二维空间中会发生什么。
这是 sigmoid 函数在非比例输入下的样子,即∑(x):
如果我们将输入放大 5 倍,即∑(5x),结果会是这样。如您所见,并且可能已经猜到,缩放输入会在 x 轴上压缩或扩展函数。
最后,向输入中添加一个标量意味着我们在 x 轴上移动函数。这里是 σ(5x - 4):
因此,通过缩放和转换输入到激活函数,我们可以移动它和拉伸它。
还要记住,一层的输出是下一层的输入。也就是说,在层 L 中产生的曲线(例如上面的任何一个)在被馈送到层 L+1 中的激活函数之前将被缩放和转换。所以现在我们需要问,缩放和转换来自层 L 中函数的输出意味着什么?缩放仅仅意味着改变信号的大小,即沿着 y 轴拉伸或压缩信号。平移显然意味着沿 y 轴移动。****
那么这给了我们什么呢?
虽然上面的讨论没有证明任何事情,但它强烈地表明,通过改变神经网络的权重和偏差,我们可以按照我们的喜好,以非线性的方式拉伸和转换输入值(甚至是单独的向量分量*)。*
还要考虑网络的深度将使权重在不同的尺度上起作用,并对总函数有不同的贡献-即早期权重在广义上改变总函数,而输出图层之前的权重在更详细的级别上起作用。
这为神经网络提供了非常高的表达能力,但代价是需要调整大量参数。幸运的是,我们不必手动调谐。我们可以让网络自我调整,使输出更好地满足我们的期望。这是通过称为梯度下降和反向传播的过程完成的,这是本系列的下一篇文章的主题,第 2 部分:梯度下降和反向传播。
现在,一个函数如何智能地行动?
我们已经得出结论,神经网络可以模拟从向量 x 到另一个向量 y 的映射(函数)。在这一点上,我们可以问:这对我们有什么帮助?
使用神经网络的一个非常常见的方法是对它从未见过的数据进行分类。因此,在结束这篇已经很长的文章之前,我将简要介绍一个分类的实际例子。我们将在后面的文章中回到这个例子。
当编写神经网络时,通常扔给它的第一个任务是手写数字的分类*(有点像机器学习中的“Hello World”)。对于这项任务,有一个由 60 000 张手写数字图像组成的数据集,称为 MNIST 数据集。分辨率为 28 x 28,每个像素中的颜色是 0 到 255 之间的灰度值。数据集被标记为,这意味着它指定了每个图像代表什么数字。*
如果我们展平每幅图像,即把图像的每一行都排成一长行,我们将得到一个大小为 28 x 28 = 784 的向量。此外,我们将灰度值归一化到 0 和 1 之间的范围。现在,如果我们可以将这个向量 x 输入到一个神经网络,并作为输出得到一个y-大小为 10 的向量,告诉网络认为输入代表什么数字(即输出向量的每个元素告诉网络给出图像是 0、1、2、…、9 的概率),那就好了。由于 MNIST 数据集被标记,我们可以训练这个网络,这实质上意味着:自动调整权重和偏差。最酷的事情是,如果训练做得正确,网络将能够对它以前从未见过的手写数字图像进行分类。
这怎么可能?
我将尝试用一个结论来解释这一点:每个输入向量 x 都可以被视为 784 维空间中的一个点。想想吧。长度为 3 的向量表示 3D 中的一个点。长度为 784 的向量代表 784D 中的一个点。由于每个像素值在 0 和 1 之间被归一化,我们知道该数据集的所有点位于单位立方体内,即在所有 784 个轴上的 0 和 1 之间。有理由认为,在这个空间中,代表一个数 N 的所有点彼此相当接近。例如,数字 2 的所有图像将在某个子空间中彼此靠近,而所有 7 的图像也将靠近,但在不同的子空间中。在设计这个网络时,我们决定输出应该是一个大小为 10 的向量,其中每个分量都是一个概率。这意味着输出是 10 维单位立方体中的一个点。我们的神经网络将 728D 立方体中的点映射到 10D 立方体中的点。
现在,在这种特殊情况下拟合网络真正的意思是用我们的神经网络功能在 784d-输入空间中找到那些子空间,并以这样一种方式转换(缩放,翻译)它们,使得它们在 10D 中明显可分。例如:我们希望数字 7 的所有输入(尽管它们可能略有不同)输出一个向量 y,其中表示数字 7 的分量接近 1,而所有其他 9 个分量接近 0。
我倾向于认为拟合过程是围绕这些子空间的收缩包装表面(超平面)。如果我们不过度收缩这些表面(这将导致所谓的过度拟合*),网络尚未看到的数字很可能仍会在正确的子空间内结束——换句话说,网络将能够说:“嗯,我以前从未见过这个数字,但它在我认为是数字 7 的子空间内”。*
这有点酷!
好的,这就是介绍。欢迎反馈!
现在,进入本系列的下一篇文章,你将学习如何训练神经网络,第 2 部分:梯度下降和反向传播。
脚注:
这篇文章描述了一个密集的前馈网络——一个多层感知器。选择这种网络设计是因为它简单易用。不过,很高兴知道有许多不同的网络,它们都适合不同类型的任务。
所以你可能会想,怎么可能用这么短的向量符号来编写整个层操作呢?如果你回忆一下矩阵与向量相乘的工作原理,你会意识到在做 Wx 时,你实际上是在反复地对 x 向量进行加权求和(W 矩阵中的每一行),从而得到一个向量。加上 b 向量就会得到 z。
严格地说,事情不是这样的。但我发现这是一个有益的精神形象。
原载于 2018 年 11 月 28 日machine learning . tobiashill . se*。*
第 2 部分:梯度下降和反向传播
在本文中,您将了解如何使用反向传播和随机梯度下降来训练神经网络。这些理论将被彻底地描述并且一个详细的例子计算被包括在内,其中权重和偏差都被更新。
这是系列文章的第二部分:
- 第一部分:基础。
- 第二部分:梯度下降和反向传播。
- 第 3 部分:用 Java 实现。
- 第四部分:更好、更快、更强。
- 第 5 部分:训练网络阅读手写数字。
- 额外 1:我如何通过数据扩充提高 1%的准确度。
- 号外 2:MNIST 游乐场。
我假设你已经读过上一篇文章,并且你对神经网络如何转换数据有一个很好的想法。如果上一篇文章需要良好的想象力(考虑多维度的子空间),那么另一方面,这篇文章在数学方面要求更高。振作起来:纸和笔。寂静的房间。仔细一想。一夜好眠。时间,耐力和努力。它会被理解的。
监督学习
在上一篇文章中,我们得出结论,神经网络可以用作高度可调的向量函数。我们通过改变权重和偏差来调整这个函数,但是很难手动改变它们。它们通常太多了,即使少一些,手工也很难得到好的结果。
好的一面是,我们可以通过训练网络,让网络自己调整这一点。这可以用不同的方法来完成。这里我将描述一种叫做监督学习的东西。在这种学习中,我们有一个被标记为的数据集,即我们已经有了该数据集中每个输入的预期输出。这将是我们的训练数据集。我们还确保我们有一个从未训练过网络的带标签的数据集。这将是我们的测试数据集,并将用于验证经过训练的网络对看不见的数据的分类有多好。
当训练我们的神经网络时,我们通过网络从训练数据集中输入一个又一个样本,并对每个样本的结果进行检查。特别是,我们检查结果与我们的预期(标签)相差多少。我们期望的和我们得到的之间的差异被称为成本(有时这被称为误差或损失)。成本告诉我们我们的神经网络在特定样本上的正确或错误程度。然后,可以使用这种方法来稍微调整网络,以便下次通过网络馈送该样本时,误差会更小。
有几种不同的成本函数可以使用(例如,见列表)。
在本文中,我将使用二次成本函数:
(有时,这也写在一个常数 0.5 前面,这将使它稍微干净,当我们区分它。我们将坚持上面的版本。)
回到我们第一部分的例子。
如果我们预期:
…并且得到了…
…成本将是:
由于成本函数写在上面,误差的大小明显地取决于网络输出和我们期望的值。如果我们用输入值来定义成本(如果我们也考虑所有权重、偏差和使用了什么激活函数,那么输入值当然与输出值相关),我们可以写成:
C = C(y,exp) = C(W,b,Sσ,x,exp) -即成本是一个函数W8、biases,该组激活函数,输入***【x***和
*成本只是所有这些输入的标量值。由于函数是连续可微的(有时只有*分段可微。例如,当使用重新激活时,我们可以为成本函数设想一个连续的丘陵和山谷景观。在更高维度中,这种景象很难想象,但是只有两个重量 W ₁和 W ₂,看起来可能有点像这样:
假设我们精确地得到了图像中红点指定的成本值(在那个简化的例子中,仅仅基于一个 W ₁和 W ₂)。我们现在的目标是改进神经网络。如果我们能够降低成本,神经网络将更好地分类我们的标记数据。优选地,我们希望在这个场景中找到成本函数的全局最小值。换句话说:所有山谷中最深的。这样做很难,而且对于神经网络这样复杂的功能,没有显式的方法。然而,我们可以通过使用称为梯度下降的迭代过程来找到一个局部最小值。局部最小值可能足以满足我们的需求,如果不是,我们可以随时调整网络设计,以获得新的成本-景观,以局部和迭代探索。
梯度下降
从多变量微积分我们知道,一个函数的梯度,在一个特定点的∇f 将是一个与曲面相切的矢量,指向函数增长最快的方向。相反,负梯度-∇f 将指向函数下降最快的方向。这个事实我们可以用来从我们当前的权重 W 计算新的权重 W ⁺:
在上面的等式中 η 只是一个叫做学习率的小常数。这个常数告诉我们,我们将使用多少梯度向量来将当前的权重集更改为新的权重集。如果选择得太小,权重将被调整得太慢,并且我们向局部最小值的收敛将花费很长时间。如果设置得太高,我们可能会过冲和错过(或者得到一个有弹性的不收敛的迭代行为)。
上面等式中的所有东西只是简单的矩阵运算。我们需要仔细研究的是成本函数相对于权重的梯度:
如你所见,我们暂时对具体的标量成本值 C 不感兴趣,而是当权重改变时成本函数改变多少 (逐个计算)。
如果我们展开纯矢量形式(等式 1),它看起来像这样:
使用渐变的好处是,它会调整那些最需要改变的权重,而那些不需要改变的权重会减少。这与负梯度向量正好指向最大下降方向的事实密切相关。要看到这一点,请再次查看上面简化的成本函数景观图像,并尝试将红色梯度向量分离为沿轴的分量向量。
梯度下降的想法在 N 维中同样有效,尽管很难将其可视化。梯度仍然会告诉哪些组件需要改变更多,哪些组件需要改变更少,以减少函数 c。
到目前为止,我只是谈论了重量。偏见呢?同样的推理对他们同样有效,但是我们计算(这更简单):
现在是时候看看我们如何计算权重和偏差的偏导数了。这会让我们进入一个更危险的领域,所以系好安全带。为了理解这一切,我们首先需要了解…
注释
本文的其余部分是符号密集型的。你将看到的许多符号和字母只是作为下标的索引,帮助我们跟踪我所指的是哪一层和哪一个神经元。不要让这些指数让数学表达式变得令人生畏。索引有助于使其更加精确。以下是如何阅读它们的简短指南:
还请注意,神经元的输入在上一篇文章中被称为(这很常见),但在这里被改为 i 。原因是我觉得把它记成输入的 i 和输出的 o 比较容易。**
反向传播
描述如何计算偏导数的最简单方法是看一个特定的单个重量:
我们还将看到,如果特定权重连接到最后一个输出层,或者连接到任何前面的隐藏层,则在如何处理偏导数方面会略有不同。
最后一层
现在考虑单个重量w的最后一层:
我们的任务是找到:
如上一篇文章所述,权重和成本函数之间有几个步骤:
- 我们将权重乘以前一层的输出,并添加一个偏差。结果是输入Iʟ**到神经元。******
- 这个 i ʟ 然后被馈入激活函数 σ ,从神经元oʟ₁产生一个输出******
- 最后这个oʟ₁用在了成本函数中。******
公平地说,很难计算…
…只是看着它。
然而,如果我们把它分成我刚才描述的三个步骤,事情会变得容易得多。由于这三个步骤是链式函数,我们可以通过使用微积分中的链式法则来分离所有步骤的导数:
事实上,这三个偏导数的计算非常简单:
这意味着我们拥有计算所需的所有三个因素
换句话说,我们现在知道如何更新最后一层中的所有权重。
让我们从这里开始往回走。
隐藏层
现在考虑单个权重【w】在隐藏层最后一层之前:
我们的目标是找到:
我们像在最后一层中那样进行。链式法则给了我们:
前两个因子与之前相同,并解析为:
- 前一层的输出和…
- 激活函数的导数。
然而,这个因素…
…有点棘手。原因是 o ʜ 的变化明显改变了最后一层中所有神经元的输入,因此,与我们只需关心来自单个最后一层神经元的一个输出相比,在更广泛的意义上改变了成本函数(我已经通过在上图中使连接更宽来说明这一点)。
为了解决这个问题,我们需要将总成本函数的偏导数分解为来自最后一层的每一个贡献。
…其中每一项都描述了当ʜ发生变化时,成本会发生多大的变化,但仅针对通过特定输出神经元发送的那部分信号。****
让我们检查一下第一个神经元的单个项。同样,如果我们用链式法则把它分开,会更清楚一点:
让我们来看看每一项:
让我们暂停一会儿,让一切都水落石出。
总结一下我们刚刚发现的情况:
- 为了知道如何更新隐藏层中的特定权重,我们根据该权重对成本进行偏导数。
- 通过应用链式法则,我们得到了三个因素,其中两个我们已经知道如何计算。
- 该链中的第三个因素是我们在上一层中已经计算过的两个因素的乘积的加权和。
- 这意味着我们可以像在上一层中那样计算该隐藏层中的所有权重,唯一的区别是我们使用来自前一层的已经计算的数据,而不是成本函数的导数。
从计算的角度来看,这是一个非常好的消息,因为我们只依赖于我们刚刚做的计算(仅最后一层),我们不必遍历更深。这有时被称为动态编程,一旦找到动态算法,这通常是加速算法的根本方法。**
由于我们仅依赖于最后一层中的一般计算(特别是我们不依赖于成本函数),我们现在可以逐层向后进行。就我们如何计算权重更新而言,任何隐藏层都不同于任何其他层。我们说我们让成本(或误差或损失)反向传播。当我们到达第一层时,我们就完成了。在这一点上,我们对等式(等式 1)中的所有权重进行偏导数,并准备在成本函数中进行梯度下降。**
你知道吗:就是这样。
嘿,等等…偏见!?
好的,我听到了。实际上它们遵循相同的模式。唯一的区别是最后一层和隐藏层表达式的导数链中的第一项将是:****
…而不是
既然输入给神经元的是oʜwʟ***+b那对 b的偏导数干脆就是 1。因此,在权重情况下,我们将链乘以上一层的输出,我们只需忽略偏差情况下的输出,然后乘以 1。*****
所有其他计算都是相同的。您可以将偏差视为权重计算的一个更简单的子案例。偏差的变化不依赖于前一个神经元的输出这一事实实际上是有道理的。这些偏置是“从侧面”添加的,而不考虑通过导线到达神经元的数据。
一个例子
从我最近陷入反向传播的境地来看,我可以想象上面的阅读可能是很难消化的。不要麻痹大意。在实践中,这是非常简单的,如果用一个例子来说明,可能所有的事情都会变得更清楚、更容易理解。
让我们以第 1 部分:基础中的例子为基础:
让我们从 w ₅:开始
通过从第 1 部分的中的正向传递中选取数据,并使用上述成本计算示例中的数据,我们将逐个因素进行计算。此外,让我们假设在本例中,我们的学习率为 η = 0.1。
同样,我们可以计算最后一层中的所有其他参数:
现在我们向后移动一层,聚焦于 w ₁ :
再一次,通过从第 1 部分的中的前向通道中选取数据,并通过使用来自上面的数据,可以直接计算出每个因素。
根据最后一层的计算:
现在我们拥有了一切,终于可以找到 w ₁ :
以同样的方式,我们可以计算隐藏层中的所有其他参数:
就是这样。我们已经成功地更新了全网的权重和偏差!
健全性检查
让我们验证一下,网络现在在输入端的表现稍好一些
x = [2,3]
第一次我们通过网络输入向量,我们得到了输出
y = [0.712257432295742,0.533358751]
…成本为 0.1988811957 美元。
现在,在我们更新了权重之后,我们得到了输出
y =[0.719269360605]54666
…并且成本为 0.18118540884。19861.886868686867
请注意,这两个组件都已向我们预期的[1,0.2]方向略微移动,网络的总成本现在更低了。
反复训练会更好!
下一篇文章将展示这在 java 代码中的表现。当您觉得准备好了,请开始:第 3 部分:用 Java 实现。
欢迎反馈!
原载于 2018 年 12 月 4 日machine learning . tobiashill . se。**
第 3 部分:Java 实现
在本文中,您将看到如何用易于理解的 java 代码实现前两篇文章中介绍的理论。完整的神经网络实现可以下载、详细检查、构建和实验。
这是系列文章的第三部分:
- 第一部分:基础。
- 第二部分:梯度下降和反向传播。
- 第 3 部分:用 Java 实现。
- 第四部分:更好、更快、更强。
- 第 5 部分:训练网络阅读手写数字。
- 额外 1:我如何通过数据扩充提高 1%的准确度。
- 号外 2:MNIST 游乐场。
我假设您已经阅读了前两篇文章,并且对神经网络的前向传递和学习/训练传递有了相当好的理解。
这篇文章会很不一样。它将用几段 java 代码来展示和描述这一切。
我所有的代码,一个完全工作的神经网络实现,可以在这里找到、检查和下载。
追求
通常在编写软件时,我会使用大量的开源软件。这样我能更快地得到好的结果,并且我能使用别人花了很多心思的好的抽象概念。
然而这一次不同了。我的目标是展示一个神经网络是如何工作的,并且只需要你了解其他开源库。如果你设法阅读 Java 8 代码,你应该没问题。这些都在几个简短而整洁的文件里。
这个野心的结果是我甚至没有导入线性代数的库。相反,创建了两个简单的类,只包含所需的操作——满足通常的猜想:Vec 和 Matrix 类。这两个都是非常普通的实现,包含典型的算术运算加、减、乘和点积。
向量和矩阵的好处在于,它们通常可以用来使代码更有表现力、更整洁。通常,当您面临对一个集合中的每个元素与另一个集合中的每个其他元素进行运算时,例如对一组输入进行加权求和,您很有可能可以将数据排列成向量和/或矩阵,并获得非常紧凑且富有表现力的代码。神经网络也不例外。
我也尽可能地通过使用 Vec 和 Matrix 类型的对象来压缩计算。结果是整洁的,离数学表达式不远。for-loops 中没有 for-loops 模糊了俯视图。然而,在检查代码时,我鼓励你确保对 Vec 或 Matrix 类型对象的任何看起来简洁的调用实际上都产生了一系列算术运算,这些运算在第 1 部分和第 2 部分中有定义。
Vec 类上的两个操作不像我上面提到的典型操作那样常见。这些是:
- 外积 (Vec.outerProduct()),它是列向量和行向量的逐元素乘法,产生一个矩阵。
- Hadamard 乘积 (Vec.elementProduct()),它只是两个向量(都是列向量或行向量)的逐元素乘法,产生一个新向量。
概观
典型的神经网络有许多层。每个层可以是任意大小,包含前一层与其自身之间的权重以及偏差。每一层还配置了一个激活和一个优化器(后面会详细介绍)。
无论何时设计高度可配置的东西,构建器模式都是一个好的选择。这为我们提供了一种非常简单易懂的方法来定义和创建神经网络:
要使用这样一个网络,通常要么给它提供一个输入向量,要么在调用evaluate()-方法时添加一个预期结果。当进行后者时,网络将观察实际输出和预期输出之间的差异,并存储这一印象。换句话说,它将反向传播错误。出于很好的原因(我将在下一篇文章中回到这一点),网络不会立即更新它的权重和偏差。要做到这一点,也就是让任何印象“深入人心”,必须调用updatefromleang()。例如:
正向输送
让我们深入研究一下 evaluate() 方法中的前馈通道。请注意下面(第 9 行和第 10 行)输入数据是逐层传递的:
在层内,输入向量与权重相乘,然后加上偏差。这又被用作激活功能的输入。见第 11 行。该层还存储输出向量,因为它用于反向传播。
(我将在下一篇文章中回到 out 变量上的 get 和 set 操作的原因。现在就把它看作是 Vec 类型的变量)
激活功能
该代码包含一些预定义的激活函数。其中每一个都包含实际的激活函数, σ ,以及导数,σ’。
成本函数
还包括一些成本函数。二次曲线看起来像这样:
成本函数有一种计算总成本(作为标量)的方法,但也有一种重要的成本函数微分法,可用于
反向传播
正如我在前面关于前馈传递的部分中提到的:如果一个预期的结果被传递给 evaluate 函数,网络将通过调用 learnFrom() -method 从中学习。请参见第 12–13 行。
在该方法中,实际的反向传播发生了。在这里,你应该能够在代码中详细地遵循第 2 部分的步骤。看到第二部分中相当冗长的数学表达式可以归结为以下内容,这也多少令人欣慰:
请注意,反向传播中的学习(偏导数)是通过调用adddeltaweightsandbias()-方法逐层存储的。
直到调用update from learning()-方法后,权重和偏差才会改变:
之所以将此设计为两个独立的步骤,是因为它允许网络观察大量样本并从中学习……然后最终更新权重和偏差,作为所有观察的平均值。这实际上叫做批量梯度下降或小型批量梯度下降。我们将在下一篇文章中回到这些变体。现在,您也可以在每次调用后调用 updateFromLearning 来评估(带着期望)以使网络在每次采样后得到改进。在每个样本之后更新网络,这被称为随机梯度下降。
这就是updateWeightsAndBias()-方法的样子。请注意,权重和偏差的所有计算更改的平均值被提供给优化器对象上的两个方法 updateWeights() 和 updateBias() 。
我们还没有真正讨论过优化器的概念。我们已经说过,权重和偏差是通过减去用一些小的学习率缩放的偏导数来更新的,即:
是的,这是一个很好的方法,也很容易理解。然而,还有其他方法。其中一些稍微复杂一些,但是提供了更快的收敛。关于如何更新权重和偏差的不同策略通常被称为优化器。我们将在下一篇文章中看到另一种方法。因此,将此作为网络可以配置使用的可插拔策略是合理的。
现在,我们只是使用简单的梯度下降策略来更新我们的权重和偏差:
差不多就是这样。通过几行代码,我们有可能配置一个神经网络,通过它输入数据,并让它学习如何对看不见的数据进行分类。这将在第 5 部分:训练网络读取手写数字中显示。
但首先,让我们把这个提升几个档次。第四部分:更好、更快、更强再见。
欢迎反馈!
原载于 2018 年 12 月 15 日machine learning . tobiashill . se。
第 4 部分:更好、更快、更强
在本文中,我们将建立在第 3 部分中介绍的基本神经网络基础上。我们将添加一些宝石,这将改善网络。影响巨大的小变化。我们将遇到的概念,如初始化、小批量、并行化、优化器和正则化无疑是您在学习神经网络时会很快遇到的东西。本文将对进行实例讲解。
这是系列文章的第四部分:
- 第一部分:基础。
- 第二部分:梯度下降反传。
- 第 3 部分:Java 实现。
- 第四部分:更好、更快、更强。
- 第五部分:训练网络阅读手写数字。
- 额外 1:我如何通过数据扩充获得 1%的更高准确率。
- 额外 2:MNIST 游乐场。
初始化
到目前为止,我们已经看到了如何通过反向传播自动调整权重和偏差,以改善网络。那么,这些参数的良好初始值是什么呢?
一般来说,您只需将-0.5 和 0.5 之间的参数随机化,取平均值 0,就可以了。
然而,在深层网络中,研究表明如果让权值与连通层(或有时两个连通层中的神经元数量成反比,则可以获得更好的收敛性。换句话说:许多神经元的初始权重接近 0。神经元越少越好。您可以在这里和这里更多地了解为什么。
我已经实现了几个比较流行的方法,并且在创建神经网络时可以将它作为一种策略注入(见第 5 行):
实现的初始化策略有:XavierNormal、XavierUniform、LeCunNormal、LeCunUniform、HeNormal、HeUniform 和 Random。还可以通过在创建图层时提供权重矩阵来明确指定图层的权重。
定量
在的上一篇文章中,我们确实简要地提到了这样一个事实,即只要我们愿意,我们就可以通过网络输入样本,然后根据我们自己的判断,选择来更新权重。在 api 中,evaluate()-方法(收集学习/印象)和updateFromLearning()*-方法(让学习深入其中)之间的分离正是为了实现这一点。它让 API 的用户决定使用以下哪种策略:*
随机梯度下降
在随机梯度下降中,我们在每次评估后更新权重和偏差。记住第二部分中的成本函数依赖于输入和期望:C = C(W,b,Sσ,x,exp)。因此,每个样本的成本情况会略有不同,负梯度将指向每个样本独特情况下的最陡下降方向。
现在,让我们假设我们有总成本景观,这将是整个数据集的平均成本函数。所有样本。在这种情况下,随机梯度下降看起来就像一次混沌漫步。
只要学习率合理地小,该行走平均来说仍然会下降到局部最小值。
问题是 SGD 很难高效地并行化。权重的每次更新都必须是下一次计算的一部分。
批量梯度下降
与随机梯度下降相反的是批量梯度下降(有时简称为梯度下降)。在该策略中,在对权重进行更新之前,评估所有训练数据。计算所有样品的平均梯度。当仔细计算小的下降步骤时,这将收敛到局部最小值。缺点是对于大型数据集,反馈循环可能会很长。
小批量梯度下降
小批量梯度下降是 SGD 和 BGD 之间的一种折衷方案——在更新权重之前,通过网络运行 N 个样本的批次。典型地,N 在 16 到 256 之间。通过这种方式,我们得到了在其下降过程中相当稳定的东西(尽管不是最佳的):
- 与批处理梯度下降相比,我们获得了更快的反馈,并且可以在可管理的数据集上运行。
- 与随机梯度下降相比,我们可以使用 CPU、GPU 或 TPU 上的所有内核并行运行整个批处理。两个世界中最好的。
并行
*只需对代码库做一些小的改动,就可以并行执行**评估()*方法。一如既往……国家是并行化的敌人。在前馈阶段,唯一需要小心处理的状态是每个神经元的输出值被存储在每个神经元中。竞争线程肯定会在数据被用于反向传播之前覆盖这些数据。
通过使用螺纹限制,这是可以处理的。
此外,权重和偏差的增量累积并最终应用的部分必须同步。
有了这些,在处理训练数据时,可以根据需要(或可用)使用任意数量的内核。
在神经网络上并行化小批量执行的典型方法如下所示:
特别注意将批次中所有样本的处理扩展为第 2 行平行流的结构。
这就是全部需要。
最棒的是:加速非常显著。
这里需要记住的一点是,以这种方式分散执行(在几个并行线程上运行一个批处理)会给计算增加熵,这有时是不希望的。举例说明:当误差增量在 add deltaweights 和 bias 方法中相加时,每次运行时它们可能会以稍微不同的顺序相加。虽然该批中的所有影响术语都是相同的,但是它们被相加的改变顺序可能会产生很小的差异*,随着时间的推移,在大型神经网络中,这些差异开始显现,导致不可再现运行。在这种操场神经网络实现中,这可能很好……但是如果你想做研究,你必须用不同的方式来处理并行性(通常是制作输入批处理的大矩阵,并在 GPU/TPU 上并行所有前馈和反向传播的矩阵乘法)。*
优化器
在最后一篇文章中,我们谈到了这样一个事实,即我们将权重和偏差的实际更新作为一种策略来实现。原因是,除了基本的梯度下降,还有其他方法。
记住,这一点:
在代码中看起来像:
尽管努力下坡,SGD(及其分批变型)受到以下事实的影响:梯度的大小与评估点的坡度成比例。更平坦的表面意味着更短的梯度长度…给出更小的步长。因此,它可能会卡在鞍点上,例如在这条路径的中途:
(正如你所看到的,在几个方向上有比困在那个鞍点更好的局部最小值)
更好地处理这种情况的一种方法是让权重的更新不仅取决于计算的梯度,还取决于上一步中计算的梯度。有点像从之前的计算中合并回声。**
一个物理类比是想象一个质量为 T3 的弹球滚下山坡。这样的弹球可能有足够的动量来保持足够的速度,以覆盖更平坦的部分,甚至爬上小山丘——这样可以逃离鞍点,继续朝着更好的局部最小值前进。**
毫不奇怪,最简单的优化器之一叫做…
动力
在动量项中,我们引入了另一个常数来告诉我们从前面的计算中需要多少回声。这个系数γ叫做动量。上面的等式 1 现在变成:
如你所见,动量优化器将最后一个增量保持为 v ,并基于前一个计算新的 v 。通常情况下, γ 可能在 0.7–0.9 的范围内,这意味着 70% — 90%的先前梯度计算对新计算有贡献。
在代码中,它被配置为(第 4 行):
并像这样应用于动量优化器:
我们如何更新权重的这个小变化通常会提高收敛性。
我们可以做得更好。另一个简单而强大的优化器是内斯特罗夫加速梯度(NAG)。
内斯特罗夫加速梯度
与动量唯一不同的是,在 NAG 中,我们在一个位置计算成本,这个位置已经结合了上次梯度计算的回波。
从编程的角度来看,这是个坏消息。到目前为止,优化器的一个很好的特性是,它们只在权重更新时应用。在这里,当计算成本函数时,我们突然需要合并已经存在的最后一个梯度 v 。当然,我们可以扩展优化器的概念,并允许它增加权重查找*。这并不一致,因为所有其他优化器(据我所知)只关注更新。*
通过一个小技巧,我们可以解决这个问题。通过引入另一个变量x**=W-γv我们可以用 x 来重写方程。具体来说,这种引入意味着有问题的成本函数将只依赖于 x ,而不依赖于历史-值(至少不明确依赖于*)。-γv部分实际上仍将被使用,但现在隐含地存储在所有权重上)。***
重写之后,我们可以将 x重新命名为,让它看起来更像我们认识的东西。等式变为:**
好多了。现在,内斯特罗夫优化器只能在更新时应用:
要阅读更多关于 NAG 如何以及为什么通常比 Momentum 更有效的信息,请查看这里的和这里的和。
当然,也有很多其他奇特的优化器可以实现。其中一些通常比 NAG 性能更好,但代价是增加了复杂性。就改进每行代码的收敛性而言 NAG 击中了一个甜蜜点。**
正规化
当我刚刚写完神经网络代码的第一个版本时,我渴望测试它。我迅速下载了 MNIST 的数据集并开始玩。几乎是立刻,我就在准确性方面达到了相当惊人的数字。与其他人在等效网络设计上达成的结果相比,我的更好——好得多。这当然让我起了疑心。
然后我突然想到。我比较了我在训练数据集上的统计数据和他们在测试数据集上的统计数据。我已经在训练数据上无限循环地训练了我的网络,甚至从未加载过测试数据集。当我看到的时候,我大吃一惊。我的网络在看不见的数据上完全是垃圾,即使它在训练数据集上接近半神。我刚刚吞下了过度拟合的苦果。
过度拟合
当你过于积极地训练你的网络时,你可能最终会处于这样一种情况:你的网络已经学会了(几乎记住了)训练数据,但作为一个副作用,它也失去了训练数据所代表的大致内容。这可以通过在网络训练期间,在训练数据与测试数据上绘制网络准确度来看出。通常,您会注意到训练数据的精确度继续增加,而测试数据的精确度最初增加,之后变得平稳,然后再次开始下降。
这是你走得太远的时候。
这里的一个观察是,我们需要能够检测到这种情况何时发生,并在那时停止训练。这通常被称为提前停止,我将在本系列的最后一篇文章第 5 部分:训练网络读取手写数字中再次提到。
但是也有一些其他的方法来降低过度拟合的风险。我将通过展示一个简单但强大的方法来结束这篇文章。
L2 正则化
在 L2 正则化中,我们试图避免网络中的个体权重变得太大(或者更精确地说:离零太远)。我们的想法是,与其让几个权重对输出产生很大的影响,不如让许多权重相互作用,每个人都做出适度的贡献。基于第 1 部分:基础中的讨论,几个更小的权重(与相比更少更大的)将给出输入数据的更平滑和不太尖锐/多峰的分离似乎并不太牵强。此外,似乎合理的是,更平滑的分离将保留输入数据的一般特征,相反:尖锐和多峰的同上将能够非常精确地去除输入数据的特定特征。**
那么我们如何避免网络中的个体权重变得太大呢?
在 L2 正则化中,这是通过向成本函数添加另一个成本项来实现的:
如您所见,大重量会导致更高的成本,因为它们是额外成本总和的平方。更小的重量根本没什么关系。
λ因子将告诉我们需要多少 L2 正则化。将其设置为零将完全消除 L2 效应。将其设置为 0.5,将使 L2 正则化成为网络总成本的重要部分。实际上,在给定网络布局和数据的情况下,您必须找到最佳的λ。
非常坦率地说,我们不太关心作为特定标量值的成本。我们更感兴趣的是当我们通过梯度下降训练网络时会发生什么。让我们看看这个新成本函数对于特定权重 k 的梯度计算会发生什么。
正如你所看到的,我们在每次更新中只得到一个额外的术语 λwk 来减去权重 wk 。
在代码中,L2 正则化可以配置为(参见第 9 行):
并以这种方式在更新时应用(参见第 3 行和第 4 行):
这种微小的变化使得神经网络实现在过拟合时更容易控制。
所以,这篇文章到此结束。在理论和实践中,已经出现了几颗小宝石……所有这些,只需要几行代码,就能使神经网络更好、更快、更强。
下一篇文章将是本系列的最后一篇,我们将看到网络在经典数据集上的表现——Mnist:第 5 部分:训练网络阅读手写数字。
欢迎反馈!
原载于 2018 年 12 月 17 日machine learning . tobiashill . se。**
第 5 部分:训练网络阅读手写数字
在这最后一篇文章中,我们将看到这种神经网络实现的能力。我们将向它投掷一个最常见的数据集(MNIST),看看我们是否可以训练一个神经网络来识别手写数字。
这是系列文章的第五部分,也是最后一部分:
- 第一部分:基础。
- 第二部分:梯度下降和反向传播。
- 第 3 部分:用 Java 实现。
- 第四部分:更好、更快、更强。
- 第 5 部分:训练网络阅读手写数字。
- 额外 1:我如何通过数据扩充提高 1%的准确度。
- 号外 2:MNIST 游乐场。
MNIST 数据集
MNIST 数据库包含手写数字,并具有 60.000 个样本的训练集和 10.000 个样本的测试集。数字在 28×28 像素的固定大小的图像中居中。
这个数据集对于任何只想探索他们的机器学习实现的人来说都是超级方便的。它只需要很少的预处理和格式化工作。
密码
这个小实验的所有代码都可以在这里找到。这个项目当然也依赖于神经网络实现。
用 java 读取数据集很简单。数据格式在 MNIST 页面中有描述。
称为 DigitData 的类中的每个数字。这个类当然包含数据(即输入)和标签(即期望)。我还添加了一个小技巧来增强 digital data 类的 toString():
调用 toString()实际上给出了数据的 ascii 阴影输出:
这在检查网络将哪些数字与其他数字混淆时非常方便。我们将在本文的最后回到这个问题。
网络安装程序
边界层由我们的数据给出:
- 输入层将图像的每个像素作为输入,大小必须为 28 x 28 = 784。
- 输出图层是一个分类。介于 0 和 9 之间的数字,即大小为 10。
隐藏层需要更多的探索和测试。我尝试了一些不同的网络布局,意识到用一个巨大的网络获得好的精确度并不难。因此,我决定减少隐藏神经元的数量,看看是否还能得到不错的结果。我认为,一个受约束的设置将教会我们更多如何获得额外百分比的准确性。我决定将最大隐藏神经元设置为 50,并开始探索我能达到的。
我很早就用一个只有两个隐藏层的漏斗状结构取得了很好的效果,并一直在探索。例如第一个有 36 个神经元,第二个有 14 个神经元。
784 个输入 36 个隐藏 14 个隐藏 10 个输出神经元
经过反复试验,我决定使用两个激活函数,这两个函数在本系列的前几篇文章中没有出现过。漏热路和软最大。
漏 ReLU 是 ReLU 的变种。唯一的区别是,对于负输入,它不是完全平坦的。相反,它有一个小的正梯度。
它们最初被设计用来解决 ReLU 的零梯度部分可能会关闭神经元的问题。也可以在 Quora 上看到这个问题,了解何时以及为什么你想测试 Leaky ReLU 而不是 ReLU 的细节。
Softmax 是一个激活函数,通常在分类时用于输出层。softmax 的好处在于它为您提供了分类概率分布— 它将告诉您输出图层中每个类的概率。假设我们通过网络发送代表数字 7 的数字数据,它可能会输出如下内容:
如你所见,数字 7 的概率最高。还要注意,概率总和为 1。
当然,Softmax 也可以与阈值一起使用,这样,如果没有一个类别获得高于该阈值的概率,我们就可以说网络没有识别出输入数据中的任何内容。
Softmax 不好的地方在于它不像其他激活函数那么简单。在正向和反向传播过程中,它变得有点难看。它实际上打破了我的激活抽象,在 softmax 的引入之前,我只能将激活定义为向前传递的函数本身 fn()和向后传播的函数的导数步骤 dFn()。
Softmax 这样做的原因是,它的 dFn() ( ∂ o/∂i )可以利用链式法则的最后一个因子,【c/∂o*,*使计算更清晰/容易。因此,必须扩展激活抽象来处理计算 ∂C/∂i 乘积。
在所有其他激活功能中,这只是一个乘法:
但是在 softmax 中是这样的(见下面的 dCdI()-function):
点击阅读更多关于 Softmax 的信息,点击阅读如何计算 Softmax 的导数。
总而言之,我的设置通常是:
准备就绪后,让我们开始训练吧!
训练循环
训练循环很简单。
- 我在每个时期之前混洗训练数据(一个时期是我们称之为的所有可用训练数据的完整训练回合,在我们的例子中是 60.000 个数字)并且通过网络馈送它,其中训练标志被设置为真。
- 在每第 5 个时期之后,我也通过网络运行测试数据并记录结果,但是在这样做的同时不训练网络。
代码如下所示:
整个数据集的运行是成批并行完成的(正如上一篇文章中已经展示的那样):
上述循环中唯一缺少的部分是知道何时停止…
提前停止
如前所述(当引入 L2 正则化时),我们真的想避免过度拟合网络。当这种情况发生时,训练数据的准确性可能仍然会提高,而测试数据的准确性开始下降。我们用 StopEvaluator 对其进行跟踪。这个实用程序类保存了测试数据的错误率的移动平均值,以检测错误率何时确实开始下降。它还存储了网络如何看待最佳测试运行(试图找到这个测试运行的峰值)。
代码如下所示:
结果
使用如上所示的网络布局(50 个隐藏神经元,内斯特罗夫和 L2 配置:如上所述),我一直将网络训练到大约 2.5% 的错误率。
记录运行的错误率只有 2.24%,但我不认为这是相关的,除非我的大部分运行都在这个比率左右。原因是:来回调整超参数,试图让在测试数据上打破记录(虽然很有趣)也可能意味着我们让过度适应测试数据。换句话说:我可能发现了一个幸运的参数组合,它碰巧表现得非常好,但在看不见的数据上仍然不是那么好。
混乱
因此,让我们来看看网络通常会混淆的几个数字:
这些只是几个例子。其他几种边界情况也是可用的。网络发现很难对这些进行分类,这是有道理的,原因可以追溯到我们在本系列第一部分的结尾部分中讨论的内容:784D 空间中的一些点距离它们的数字组太远,可能更接近其他一些点/数字组。或者用自然语言来说:它们看起来更像其他数字,而不是它们应该代表的数字。
这并不是说我们不关心这些棘手的案件。恰恰相反。这个世界是一个真正模糊的地方,机器学习需要能够处理模糊性和细微差别。你知道,要少一点机器味(“肯定”),多一点人性(“没问题”)。但我认为这表明解决方案并不总是在手边的数据中。通常情况下,上下文给出了人类正确分类(或理解)某事物所需的线索……比如一个写得很糟糕的数字。但这是另一个大而有趣的话题,不在本文的介绍范围之内。
包裹
这是本系列的第五部分,也是最后一部分。写这篇文章的时候我学到了很多,我希望你通过阅读它也能学到一些东西。
欢迎反馈!
进一步阅读的好资源
这些是我发现比大多数其他人更好的资源。如果你想有更深一层的了解,请深入研究:
- 神经网络上的 3 蓝 1 棕精彩视频 (4 集)
- 迈克尔·尼尔森精彩教程(几章)
- 斯坦福课程 CS231n 。
原载于 2018 年 12 月 28 日machine learning . tobiashill . se。
基于隐马尔可夫链模型的词性标注
Photo Credit: Pixabay
词性标注 (POS)是用名词、动词、形容词、副词等词性对句子进行标注的过程。
隐马尔可夫模型 (HMM)是一个简单的概念,可以解释大多数复杂的实时过程,如语音识别和语音生成、机器翻译、生物信息学的基因识别、计算机视觉的人类手势识别等等。
在本帖中,我们将使用石榴库为词性标注构建一个隐马尔可夫模型。我们不会深入统计词性标注器的细节。不过,如果你有兴趣,这里有纸。
数据
导入库并读取数据。
import matplotlib.pyplot as plt
import numpy as npfrom IPython.core.display import HTML
from itertools import chain
from collections import Counter, defaultdict
from pomegranate import State, HiddenMarkovModel, DiscreteDistribution
import randomSentence = namedtuple("Sentence", "words tags")def read_data(filename):
"""Read tagged sentence data"""
with open(filename, 'r') as f:
sentence_lines = [l.split("\n") for l in f.read().split("\n\n")]
return OrderedDict(((s[0], Sentence(*zip(*[l.strip().split("\t")
for l in s[1:]]))) for s in sentence_lines if s[0]))def read_tags(filename):
"""Read a list of word tag classes"""
with open(filename, 'r') as f:
tags = f.read().split("\n")
return frozenset(tags)Sentence = namedtuple("Sentence", "words tags")def read_data(filename):
"""Read tagged sentence data"""
with open(filename, 'r') as f:
sentence_lines = [l.split("\n") for l in f.read().split("\n\n")]
return OrderedDict(((s[0], Sentence(*zip(*[l.strip().split("\t")
for l in s[1:]]))) for s in sentence_lines if s[0]))def read_tags(filename):
"""Read a list of word tag classes"""
with open(filename, 'r') as f:
tags = f.read().split("\n")
return frozenset(tags)class Subset(namedtuple("BaseSet", "sentences keys vocab X tagset Y N stream")):
def __new__(cls, sentences, keys):
word_sequences = tuple([sentences[k].words for k in keys])
tag_sequences = tuple([sentences[k].tags for k in keys])
wordset = frozenset(chain(*word_sequences))
tagset = frozenset(chain(*tag_sequences))
N = sum(1 for _ in chain(*(sentences[k].words for k in keys)))
stream = tuple(zip(chain(*word_sequences), chain(*tag_sequences)))
return super().__new__(cls, {k: sentences[k] for k in keys}, keys, wordset, word_sequences,
tagset, tag_sequences, N, stream.__iter__)def __len__(self):
return len(self.sentences)def __iter__(self):
return iter(self.sentences.items())class Dataset(namedtuple("_Dataset", "sentences keys vocab X tagset Y training_set testing_set N stream")):
def __new__(cls, tagfile, datafile, train_test_split=0.8, seed=112890):
tagset = read_tags(tagfile)
sentences = read_data(datafile)
keys = tuple(sentences.keys())
wordset = frozenset(chain(*[s.words for s in sentences.values()]))
word_sequences = tuple([sentences[k].words for k in keys])
tag_sequences = tuple([sentences[k].tags for k in keys])
N = sum(1 for _ in chain(*(s.words for s in sentences.values())))
# split data into train/test sets
_keys = list(keys)
if seed is not None: random.seed(seed)
random.shuffle(_keys)
split = int(train_test_split * len(_keys))
training_data = Subset(sentences, _keys[:split])
testing_data = Subset(sentences, _keys[split:])
stream = tuple(zip(chain(*word_sequences), chain(*tag_sequences)))
return super().__new__(cls, dict(sentences), keys, wordset, word_sequences, tagset,
tag_sequences, training_data, testing_data, N, stream.__iter__)def __len__(self):
return len(self.sentences)def __iter__(self):
return iter(self.sentences.items())data = Dataset("tags-universal.txt", "brown-universal.txt", train_test_split=0.8)print("There are {} sentences in the corpus.".format(len(data)))
print("There are {} sentences in the training set.".format(len(data.training_set)))
print("There are {} sentences in the testing set.".format(len(data.testing_set)))
语料库中有 57340 个句子。
训练集中有 45872 个句子。
测试集中有 11468 个句子。
看一眼数据。句子
key = 'b100-38532'
print("Sentence: {}".format(key))
print("words:\n\t{!s}".format(data.sentences[key].words))
print("tags:\n\t{!s}".format(data.sentences[key].tags))
句子:b100–38532
词语:
(‘也许’,‘它’,‘曾经’,‘对’,‘对’,’, ‘;’)
标签:
(‘ADV’,‘PRON’,‘动词’,‘ADJ’,’, ‘.’)
统计语料库中的独特元素。
print("There are a total of {} samples of {} unique words in the corpus."
.format(data.N, len(data.vocab)))
print("There are {} samples of {} unique words in the training set."
.format(data.training_set.N, len(data.training_set.vocab)))
print("There are {} samples of {} unique words in the testing set."
.format(data.testing_set.N, len(data.testing_set.vocab)))
print("There are {} words in the test set that are missing in the training set."
.format(len(data.testing_set.vocab - data.training_set.vocab)))
语料库中共有 1161192 个样本 56057 个独特词。
训练集中有 50536 个唯一词的 928458 个样本。
测试集中有 25112 个唯一词的 232734 个样本。
测试集中有 5521 个词是训练集中缺失的。
使用数据集访问单词。x 和带有数据集的标签。Y
for i in range(2):
print("Sentence {}:".format(i + 1), data.X[i])
print()
print("Labels {}:".format(i + 1), data.Y[i])
print()
句子 1:(‘先生’,‘波德格’,‘曾’,‘谢过’,‘他’,‘庄重地’,‘和’,‘现在’,‘他’,‘造了’,‘用了’,‘的’,‘了’,‘建议’,‘。’)
标签 1:(‘名词’,‘名词’,‘动词’,‘动词’,’ PRON ',‘等)、‘康吉’、’ ADV ‘、’ PRON ‘、‘动词’、‘名词’、’ ADP ‘、’ DET '、‘名词’、'等)
第二句:(‘但是’,‘那里’,‘似乎’,‘要’,‘成为’,‘一些’,‘不同’,‘的’,‘意见’,‘作为’,‘要’,‘如何’,‘远’,‘板’,‘应该’,‘去’,‘,‘和’,‘谁的’,‘建议’,‘它’,‘应该’,‘跟随’,’)
标签 2: (‘CONJ ‘,’ PRT ‘,‘动词’,’ PRT ‘,‘动词’,’ DET ‘,‘名词’,’ ADP ‘,’ ADP ‘,’ ADV ‘,’ ADV ‘,’ DET ‘,‘名词’,‘动词’,‘动词’,’、’ CONJ ‘、’ DET ‘、‘名词’、’ PRON ‘、‘动词’、‘动词’、’)
了解 data.stream()的工作原理。
print("\nStream (word, tag) pairs:\n")
for i, pair in enumerate(data.stream()):
print("\t", pair)
if i > 3: break
流(词、标记)对:(“先生”、“名词”)
(‘波德格’,‘名词’)
(‘曾’,‘动词’)
(‘谢’,‘动词’)
(‘他’,‘PRON’)
构建一个最频繁的类标记器(MFC)
对计数
def pair_counts(tags, words):
d = defaultdict(lambda: defaultdict(int))
for tag, word in zip(tags, words):
d[tag][word] += 1
return d
tags = [tag for i, (word, tag) in enumerate(data.training_set.stream())]
words = [word for i, (word, tag) in enumerate(data.training_set.stream())]
MFC 标记器
FakeState = namedtuple('FakeState', 'name')class MFCTagger:
missing = FakeState(name = '<MISSING>')
def __init__(self, table):
self.table = defaultdict(lambda: MFCTagger.missing)
self.table.update({word: FakeState(name=tag) for word, tag in table.items()})
def viterbi(self, seq):
"""This method simplifies predictions by matching the Pomegranate viterbi() interface"""
return 0., list(enumerate(["<start>"] + [self.table[w] for w in seq] + ["<end>"]))
tags = [tag for i, (word, tag) in enumerate(data.training_set.stream())]
words = [word for i, (word, tag) in enumerate(data.training_set.stream())]word_counts = pair_counts(words, tags)
mfc_table = dict((word, max(tags.keys(), key=lambda key: tags[key])) for word, tags in word_counts.items())mfc_model = MFCTagger(mfc_table)
用模型做预测。
def replace_unknown(sequence):
return [w if w in data.training_set.vocab else 'nan' for w in sequence]def simplify_decoding(X, model):
_, state_path = model.viterbi(replace_unknown(X))
return [state[1].name for state in state_path[1:-1]]
MFC Tagger 解码序列示例
for key in data.testing_set.keys[:2]:
print("Sentence Key: {}\n".format(key))
print("Predicted labels:\n-----------------")
print(simplify_decoding(data.sentences[key].words, mfc_model))
print()
print("Actual labels:\n--------------")
print(data.sentences[key].tags)
print("\n")
Figure 1
看起来不错。
评估模型准确性。
def accuracy(X, Y, model):
correct = total_predictions = 0
for observations, actual_tags in zip(X, Y):
# The model.viterbi call in simplify_decoding will return None if the HMM
# raises an error (for example, if a test sentence contains a word that
# is out of vocabulary for the training set). Any exception counts the
# full sentence as an error (which makes this a conservative estimate).
try:
most_likely_tags = simplify_decoding(observations, model)
correct += sum(p == t for p, t in zip(most_likely_tags, actual_tags))
except:
pass
total_predictions += len(observations)
return correct / total_predictions
评估 MFC 标记的准确性。
mfc_training_acc = accuracy(data.training_set.X, data.training_set.Y, mfc_model)
print("training accuracy mfc_model: {:.2f}%".format(100 * mfc_training_acc))mfc_testing_acc = accuracy(data.testing_set.X, data.testing_set.Y, mfc_model)
print("testing accuracy mfc_model: {:.2f}%".format(100 * mfc_testing_acc))
训练准确率 mfc_model: 95.72%
测试准确率 mfc_model: 93.01%
很好,让我们看看我们是否能做得更好!
构建一个隐马尔可夫模型(HMM)标记器
def unigram_counts(sequences):return Counter(sequences)tags = [tag for i, (word, tag) in enumerate(data.training_set.stream())]
tag_unigrams = unigram_counts(tags)
二元组计数
def bigram_counts(sequences):d = Counter(sequences)
return dtags = [tag for i, (word, tag) in enumerate(data.stream())]
o = [(tags[i],tags[i+1]) for i in range(0,len(tags)-2,2)]
tag_bigrams = bigram_counts(o)
序列开始计数
def starting_counts(sequences):
d = Counter(sequences)
return dtags = [tag for i, (word, tag) in enumerate(data.stream())]
starts_tag = [i[0] for i in data.Y]
tag_starts = starting_counts(starts_tag)
序列结束计数。
def ending_counts(sequences):
d = Counter(sequences)
return dend_tag = [i[len(i)-1] for i in data.Y]
tag_ends = ending_counts(end_tag)
嗯 Tagger
basic_model = HiddenMarkovModel(name="base-hmm-tagger")tags = [tag for i, (word, tag) in enumerate(data.stream())]
words = [word for i, (word, tag) in enumerate(data.stream())]tags_count=unigram_counts(tags)
tag_words_count=pair_counts(tags,words)starting_tag_list=[i[0] for i in data.Y]
ending_tag_list=[i[-1] for i in data.Y]starting_tag_count=starting_counts(starting_tag_list)#the number of times a tag occured at the start
ending_tag_count=ending_counts(ending_tag_list) #the number of times a tag occured at the endto_pass_states = []
for tag, words_dict in tag_words_count.items():
total = float(sum(words_dict.values()))
distribution = {word: count/total for word, count in words_dict.items()}
tag_emissions = DiscreteDistribution(distribution)
tag_state = State(tag_emissions, name=tag)
to_pass_states.append(tag_state)basic_model.add_states()start_prob={}for tag in tags:
start_prob[tag]=starting_tag_count[tag]/tags_count[tag]for tag_state in to_pass_states :
basic_model.add_transition(basic_model.start,tag_state,start_prob[tag_state.name])end_prob={}for tag in tags:
end_prob[tag]=ending_tag_count[tag]/tags_count[tag]
for tag_state in to_pass_states :
basic_model.add_transition(tag_state,basic_model.end,end_prob[tag_state.name])transition_prob_pair={}for key in tag_bigrams.keys():
transition_prob_pair[key]=tag_bigrams.get(key)/tags_count[key[0]]
for tag_state in to_pass_states :
for next_tag_state in to_pass_states :
basic_model.add_transition(tag_state,next_tag_state,transition_prob_pair[(tag_state.name,next_tag_state.name)])basic_model.bake()
HMM 标记的解码序列示例。
for key in data.testing_set.keys[:2]:
print("Sentence Key: {}\n".format(key))
print("Predicted labels:\n-----------------")
print(simplify_decoding(data.sentences[key].words, basic_model))
print()
print("Actual labels:\n--------------")
print(data.sentences[key].tags)
print("\n")
Figure 2
评估 HMM 标记器的准确性。
hmm_training_acc = accuracy(data.training_set.X, data.training_set.Y, basic_model)
print("training accuracy basic hmm model: {:.2f}%".format(100 * hmm_training_acc))hmm_testing_acc = accuracy(data.testing_set.X, data.testing_set.Y, basic_model)
print("testing accuracy basic hmm model: {:.2f}%".format(100 * hmm_testing_acc))
训练准确率基本 hmm 模型:97.49%
测试准确率基本 hmm 模型:96.09%
我们的 HMM 标记确实改善了结果,
现在我们已经完成了模型的构建。源代码可以在 Github 上找到。我期待听到反馈或问题。
你的部分工作正在自动化。为什么这是一件好事。
关于在线会计软件生态系统的一些知识。
我和我的团队开始进入在线会计生态系统已经一年了。自从我们开始建立公司以来,我一直在反思我们的主要观察和学习。如果我不分享一些关于技术进步的想法,我会给我们的簿记员和会计(B/A)合伙人造成伤害。
技术每天都在继续扰乱个人和职业生活。许多职业被迫重新构想他们的商业模式,或者最终变得过时。对于会计和金融行业的数百万专业人士来说,不幸的是,这些变化正在到来,而且比你想象的要快。
事情正在发生变化,而且速度很快
我们所知道的是,尽管有一些炒作和许多“人工智能垃圾”,但许多进步都比想象的要远。技术绝对是工作的某些方面的自动化,但它也将帮助 B/A 的发展和开拓新的领域——在更短的时间内,从任何地方提供比现在更高的客户价值。接下来是。
报税自动化
纳税申报自动化在不久的将来将会普及。当我提到纳税申报表时,我指的是占大多数的“简单”纳税申报表。帮助报税的数字系统已经存在了相当长的一段时间,这意味着有大量的历史数据可用于使用机器学习技术训练模型。如果你需要简单解释什么是 AI,请阅读(AI——从育儿角度的解释)。
假设处理这些数据的数据科学家是一流的,当这些系统连接到保存当前纳税年度数据的关键财务系统时,应该能够提供高质量的自动化结果。
复杂的纳税申报无论是涉及复杂的公司结构还是外国资产,都需要真正的专业人士的规划和专业知识。所以,如果有人向你推销一个自动化这种复杂性的系统,在可预见的未来,要非常怀疑。有一堆炒作,所以企业主不应该太快放弃真正的专业帮助。
交易分类
对于那些简单地将账户名称应用于通过数字手段很好地流入会计软件的交易的人来说,这一领域可能是对在线 B/A 生态系统的最大破坏。
我之所以说事务分类即将到来,是因为它不是一个难以解决的机器学习问题。Intuit、Xero、Sage 等拥有系统上所有用户的大量交易数据,这使他们能够训练模型,将交易分类到特定于用户会计科目表的账户中,所有这些都是自动进行的。
一个简单的例子是航班的费用交易。一个模型将能够看到像加拿大航空公司或西捷航空公司这样的航班供应商这样的参数,并知道这将是在 T2 旅行类型账户费用的某个地方。请注意,除了供应商名称之外,还有其他参数也可以用于分类,例如地点、银行交易 id、金额、日期等。只知道后台有很多数据可以帮助训练好的模型的准确性。
当然,并不是每个企业都使用完全相同的账户名称,或者以相同的方式对某项交易进行编码。一家企业可能将所有航班编码为飞往普通差旅的航班,而另一家企业可能编码为飞往普通差旅或会议的航班,这取决于飞行费用的用途。这些看似复杂的场景,但在机器学习的世界中却很简单。
Using Machine Learning to build a simple classification model (http://briandolhansky.com/)
你看,模型会根据 B/A 为该客户所做的编码工作对交易进行分类。因此,本质上,系统正在学习每一个编码的新事务,并为将来的分类更新概率分布。简化这一过程的原因是,许多中小型企业的大部分费用会随着时间的推移而有所重复,因此系统的准确性应该高得多,而复杂性则低得多。
将会有一些罕见的或新的交易进入系统,需要进行账户分类。即使客户的模型之前可能没有经历过这些交易,但是很可能至少一些使用相同在线会计软件供应商的客户具有参数与新的参数非常匹配的交易。短期内您将看到的是一个“建议的”账户分类,它必须被 B/A 或 SMB 接受,因为对系统没有足够信心的交易进行自动分类是很危险的。也就是说,训练有素的模特变得真正擅长这个只是时间问题。
中小企业确实需要帮助,即使他们不愿意承认
如果你是一名 B/A,前几段你担心被自动解雇,不要太担心,因为我将要讨论的最新趋势将提供许多机会之一来重塑你的业务,并为你的客户提供更高水平的价值。
一个证据充分的事实是,当你的收入低于某个门槛时,雇佣一个专门的首席财务官是没有意义的。我们注意到的是,B/A 生态系统确实接受了这一事实,并在许多情况下试图重新构想他们的商业模式。他们不再仅仅是编写交易代码或拿走一鞋盒收据,而是在某种程度上充当中小型企业的兼职首席财务官。
这对 B/A 和 SMB 社区来说都是一个令人难以置信的发展,因为它允许 B/A 向 SMB 提供增值服务,同时 SMB 获得金融专业知识,帮助他们在与可能入侵其地盘的大公司的竞争中保持竞争力。
大家双赢!听起来很棒,对吧?没那么快。
我参与创办的上一家公司生产高级心脏图像处理软件。我们达到了早期增长曲线,并超过了收入门槛,在这个门槛上,除了其他职责之外,我接管首席财务官的角色是有意义的。本质上,我是以兼职首席财务官的身份开始公司的这一阶段的。
在我担任这一职位的几年里,我可以毫无疑问地说,一个连企业的某些运作都不懂的首席财务官真的没什么价值。首席财务官的工作是了解财务和运营如何协同工作,以最大限度地提高业务的增长和效率。仅仅知道如何编码交易和报税并不是首席财务官该做的。我高度警告 B/A 社区中的任何人,如果你不打算了解你的客户的至少一点运作,或者这种对你的业务的“重新想象”真的不会发生,就不要把自己推销成 CFO。
一个好的首席财务官不仅能提供强有力的财务指导,还能让中小企业的所有者和管理者以更明智、更高效的方式经营企业。首席财务官是财务和运营数据的连接点,需要提供有根据的见解和建议。这看起来很明显,但是在我们所有的交流中,我们看到了客户想要什么和他们实际想要什么之间的脱节。
在公司历史的早期,我们进行了用户体验(UX)研究。在我们采访的 40 家中小企业中,除了了解财务调节和一些一般收入和支出信息外,他们都认为标准财务报表没有什么价值。这可能会引起一些人的警觉,他们以报表为客户提供的增值成果而自豪,但同时他们也应该意识到提供价值的机会不仅仅是报表。
说起来容易做起来难
今年我们接触到的 B/a 都是非常棒的人,他们非常关心他们的客户。然而,他们仍然有业务要经营,不能花费大量额外的时间来导出数据,将数据输入电子表格,进行复杂的计算,并试图制作某种用户会发现其价值的运营报告。
即使 B/A 从一开始就真正了解他们客户的运营,这样做也是不经济的。因此,我们遇到了另一个脱节的问题,即如何在提供运营级价值的同时,仍然能够运营盈利的业务。
将帮助你进化的技术(以及为什么我们早上醒来)
在 chata.ai,我们一直在讨论如何成为实现这种商业模式转型的催化剂。创新需要解决两个主要领域:
1) 信息检索和报告需要尽可能的快捷和直观。
已解决:chata.ai 允许您提出问题并获得答案,不再需要导出、电子表格或不完整的基于仪表板的软件。
更好地了解 B/A 客户的日常兴趣所在。
求解:
对于我们来说,通过 B/A 社区实现这些业务模式转型的一个关键因素是让 chata.ai 掌握在他们合作的 SMB 手中。
chata.ai 最吸引人的地方在于它非常简单易用。除了知道如何写短信(几乎每个人都知道如何写),中小型企业用户几乎不需要系统培训。中小型企业用户每天都在开展业务。大多数中小型企业都有关于其业务的问题,但他们往往得不到答案,因为数据被困在一些他们不知道如何使用的系统中,无法访问。
如果 B/A 用户能够看到他们的中小型企业客户提出的自然语言问题,这将提供令人难以置信的洞察力,并允许 B/A 为中小型企业提供全新水平的价值和专业知识。剧透警告——这个的 B/A 接口可能正在开发中。
在 chata.ai,我们每天都会问自己一些问题,比如“我们如何帮助 B/A 社区为他们的客户增加更多价值?”,“我们如何通过支持使用数据资产来解决中小型企业的日常问题?”,“我们如何优化时间/价值曲线,使各方都走在前面?”。
我们的最终目标是帮助我们的合作伙伴取得成功。这些都是复杂的问题,就像任何优秀的创新者应该做的那样,我们继续在这个不可思议的生态系统中倾听人们的声音,适应需要的地方,并总是,总是推动我们自己的界限。
建立一个能在你自己的游戏中打败你的人工智能
11 岁时,我发明了一种桌游。 我没想到会有什么结果。八年,三个出版商,两个代理商,一个 疯狂的故事 后来,Pathwayz 上架了。这款游戏赢得了 2014 年的玩具博士最佳选择,并在 2015 年被门萨推荐。Pathwayz 已经在网上销售,在 Barnes & Noble,以及北美的数百家商店。
Pathwayz 类似于奥赛罗和围棋。人工智能已经 已经 掌握了那些游戏。1997 年,物流泰洛击败奥赛罗世界冠军。2016 年,AlphaGo 战胜围棋世界冠军。但是从来没有人为 Pathwayz 创造过人工智能。2017 年,迈克尔·塔克、尼克尔·普拉巴拉和我决定改变这种状况。我们参加了 CS 221:人工智能——原理与技术*。我们应用我们所学到的知识来构建 PAI,这是世界上最好的(也是唯一的)Pathwayz AI。*
我们的主要挑战是游戏中的大 树的大小 。我们实验了各种 反射 和 搜索 型号,其中我们通过括号打**。我们最好的 PAI 由 52 个特定于域的特征组成,通过时间差异学习在 5,000 多个游戏中训练权重,应用于窄束 minimax 算法。对抗业余人类玩家,PAI 在 33 场比赛中赢了 33 场。在与我们队的比赛中,PAI 以 5 比 0 击败了 Nikhil Prabala,4 比 1 击败了 Michael Tucker,3 比 2 击败了 me。22 岁,在自己的游戏上输给了一台电脑。
派的打法教会了我们 新策略 。PAI 通过与自己对弈,也让我们试探了一下;在 1000 场比赛中,第一名球员的胜率为 49.4%,因此似乎没有明显的球员优势。**
这篇文章详细记录了我们如何创造 PAI。我正在分享这项工作,以便其他人可以从中学习和借鉴。我们的 PAI 版本非常熟练,因为它没有使用神经网络。我们精心选择的功能,加上前瞻功能,使得顶级玩家的开发变得更加高效。在 下一次迭代 中,神经网络和其他改进呈现出超人发挥的潜力。
我已经包括了基本的术语解释,因此接触较少的读者可以了解更多关于 Pathwayz 和/或 AI 的信息。因此,这是一个很长的帖子。随意跳到你觉得有趣或相关的部分。或者你可以完全跳过阅读,而是在这里 与 PAI 版本对战。我建议按这个顺序玩:1。PAI 基线,2。PAI 高级基线,3。派·TDL 和 4。派 TDL 光束极小化。
path wayz 简介
Pathwayz 是一款双人战略游戏。就像在《奥赛罗》和《围棋》中一样,你必须提前计划以智胜对手。一个玩家是白人;另一个是黑色的。目标是用你的颜色在 8x12 木板的长边上建立一条路径。路径可以是正交的,也可以是对角线的,可以在任何方向来回交织。
White just won this game by connecting the left and right sides of the board.
每回合,你可以放置一个你颜色的普通棋子或一个对手颜色的永久棋子。你为什么要给你的对手一个永久的棋子?因为它翻转周围的棋子。
A: White places a regular piece, circled red. B: Black places a permanent piece, which flips the four adjacent pieces.
玩游戏的人工智能时间线
这是一个玩游戏的人工智能的时间轴:
From Deep Blue to AlphaGo, AIs have defeated the world champions of Chess, Go, and other board games.
人工智能现在比人类更擅长西洋双陆棋、跳棋、国际象棋、奥赛罗和围棋。参见 Audrey Keurenkov 的关于 AlphaGo 之前人工智能游戏的“简要”历史,以获得更深入的时间线。
2017 年,迈克尔·塔克(Michael Tucker)、尼基尔·普拉巴拉(Nikhil Prabala)和我着手为 Pathwayz 打造全球首个 AI PAI。奥赛罗和西洋双陆棋的人工智能与我们的 PAI 开发尤其相关。
与 Pathwayz 一样,奥赛罗是一种相对年轻的游戏——至少与古代的双陆棋、跳棋、国际象棋和围棋相比是如此。没有一个大规模的大师级游戏数据库来训练人工智能。相反, Logistello 和其他奥赛罗人工智能依赖于领域特定功能**(即奥赛罗特有的战略元素)。**
步步高 AI TD-Gammon,通过时间差学习**(一种强化学习)取得成功。它通过与自己对抗来学习,一路调整特征值。**
PAI 同样使用特定领域的特征和时间差异学习。我将在这篇文章的后面讨论这些技术。
数百万、数十亿、数万亿、数不清的动作
构建一个玩游戏的人工智能的难度取决于游戏的树的大小。树的大小取决于的宽度和的深度**。**
考虑井字游戏的树大小作为一个简单的例子。当棋盘是空的时,有 9 种可能的移动:9 的宽度。第一步后,有 8 个可能的移动:宽度 8。在这种情况下(以及在其他布局游戏中),宽度随着棋盘填满而减小:9、8、7、6、5、4、3、2、1。
井字游戏最多有 9 层层**。(“Ply”只是对单个玩家回合的花哨称呼。在一些游戏中,单个玩家的回合被认为是“一回合”,1 回合= 1 回合。在其他游戏中,两个玩家的回合都被认为是“一回合”,1 回合=回合。为了避免混淆,我还是坚持用“ply”。)假设我赢了 5 层。那个游戏的深度是 5。井字最大深度为 9。**
总博弈树由所有可能的移动组合组成。
Here is a small portion of tic-tac-toe’s game tree, just the first three plies of a single branch.
我们可以用宽度对深度的幂来估计井字游戏的总树大小。我们假设每个人都是很好的井字游戏玩家,那么所有游戏都以平局告终,占满了整个棋盘。在本例中,深度为 9。平均宽度为 5。如果我们从宽度到深度,或 5⁹,我们得到大约 1.95 * 10⁶,这是井字游戏(超级近似)的树的大小。我们可以使用类似的方法来估计其他游戏的树的大小。
Here is the complexity — approximate breadth, depth, and tree size — of Pathwayz, Chess, and Go.
我们在构建 PAI 时面临的主要挑战是它的树很大。6.60 * 10 的⁷很大。这比地球上沙粒的数量还要多。事实上,6.60 * 10 ⁷比填满整个宇宙的沙粒数还要多。
在这方面,Pathwayz 让人想起国际象棋和围棋。它的树比国际象棋的树大。它的树宽介于国际象棋和围棋之间。为了处理如此复杂的树,我们尝试了不同的方法。这些方法分为两类:
- 反射模型(随机、基线、对抗基线、手加权智能特征和 TDL 加权智能特征),其中 PAI 只是评估其当前的一组移动。
- 搜索模型(蒙特卡罗树搜索,expectimax,minimax,beam minimax,和 narrowing beam minimax)其中 PAI 向前看,探索博弈树。
反射模型:应用评估函数
我们使用了一个基于状态的模型来模拟博弈树。状态描述当前玩家和棋盘布局。
每一层,PAI 都会查看所有合法的步骤,并选择它认为“最好”的一个它如何决定哪一步是最好的?PAI 使用一个评估函数**,计算给定游戏状态的值。我们试验了各种可能的功能。**
一级:随机
PAI 选择随机移动。这显然不是一个好策略…
第二级:基线
PAI 选择最大化myLongestPath的移动:PAI 在连续路径中遍历的列数。
White’s myLongestPath is 4, and black’s is 5 (note that the two unconnected pieces do not contribute).
当一个策略在 50%的时间里表现得比基线更好时,我们知道这个策略是合理的。
第三级:对抗基线
PAI 选择最大化my longestpath–0.4 * yourlengestpath的移动。由于 PAI 现在推进了自己的路径并且阻挡了对手的路径,因此游戏效果显著提高。对抗性底线有时会击败新手。**
当一个策略在 50%的情况下比对手的基线表现得更好时,我们知道这个策略是好的。
第 4 级:手动加权智能功能
myLongestPath 和 yourLongestPath 只是评估游戏状态的两种方式。还有数不清的其他因素表明一个球员做得有多好。比如,你们每个人有几件永久的?比你的对手多还是少?你控制多少列?你的道路容易改变吗?容易被屏蔽?你使用对角线、分叉或直线队形吗?等等。
这些被称为特定领域特性或智能特性**。迈克尔、Nikhil 和我一起想出了 440 个可能的特征。我们缩小到我们最喜欢的特征。我们最好的 PAI 只使用 52,原因我稍后解释。**
我们设计了一些方法来衡量每个玩家的以下方面:
- 路径长度
- 路径持久性
- 列数
- 永久件数
- 带有 x 空邻居的常规块的数量
- 一次可以翻转的棋子数量
我们的突破是建立前瞻功能。通过存储路径的左右边界,我们能够有效地估计:
- 未来路径长度
- 如果一条路径被阻塞
- 如果有人离胜利只有一步之遥
我们创建了各种其他特性,包括平方、交互和指示器特性。我们精心挑选了权重**:每个特征的正值或负值。我们给我们认为更重要的特征分配更高的权重。例如,我们尝试给 myLongestPath 权重 10, yourLongestPath 权重-6,给*myNumPerm—my numberm—我的永久块数量—权重 3。*
我们通过标准化特征值(即,将它们全部压缩到相同的范围内),乘以它们的权重,并将结果相加,创建了一个评估函数。我们还创建了一个+/-一百万的效用函数**,增加了一个玩家在输赢情况下的评价函数。**
牌型灵巧的牌手是个好牌手。作为 Pathwayz 的发明者,我玩过成千上万的游戏,可能比任何人都多,非常适合体重特征。然而,我主要是凭直觉来权衡的。在我们最喜欢的 52 个特性中,我不知道它们的重要性到底有多大。这就是时差学习的作用。
第 5 级:TDL 加权智能特性
PAI 通过时间差学习** (TDL)学习特征权重。对于每一层,PAI 使用其当前重量来评估纸板——将该值存储在 V1 中。然后 PAI 移动,对手移动,PAI 再次使用它的重量来评估棋盘——存储这个新值 V2。在 TDL,奖励预测误差® 是 V2 减去 V1。换句话说,你的错误是你最初的评估函数与新的董事会状态相比有多错误。学习速率 alpha (α) 就是你学习的速率。alpha 值为 1.0 意味着您会完全忽略旧值,只考虑最新的数据。alpha 值为 0.5 意味着您对旧值和最新数据进行了平均。大多数阿尔法值要小得多(我们实验了 0.00001 到 0.01),这样你就可以逐渐学习,随着时间的推移积累更强的证据。**
每两层,PAI 计算奖励预测误差®。给定学习率(α),PAI 更新其特征权重向量,增加 R * α。如果 PAI 在两层之后的表现好于预期,则董事会之前的特征会得到更积极的重视。如果 PAI 在两层之后表现得比预期的差,那么特性的权重会更负。我们为赢/输提供了一大笔正面/负面的游戏结束奖励。PAI 在 10,000 场比赛中与自己的版本进行了比赛,以了解其领域特定功能的权重。
TDL 的要素是开发和探索**。我们希望 PAI 利用它所学到的东西,做出越来越好的动作。然而,我们也希望 PAI 继续探索,而不是过于专注于一个场景。如果 PAI 总是下“最好的”当前棋,它可能会卡在局部最小值中,忽略更好的路径。因此,我们使用了探索概率。PAI 根据随机ε(如果ε <得分最高的棋步,如果ε <得分第二高的棋步,如果ε < ⅞得分第三高的棋步等)探究了其十大棋步。).**
我们还想调整权重,所以我们创建了一个自动化的训练管道**。如果 PAI 在训练中赢了太多的游戏,它就会变得过于自信,增加它所有的特征权重(包括那些应该是负的)。在赢得至少 60%的游戏后,PAI 的对手采用 PAI 的当前权重,这确保了 PAI 总是在适当的技能水平上受到挑战。**
相反,如果 PAI 输了太多的游戏,它会变得悲观,减少所有的特征权重(包括应该是正的)。在赢了不到 40%的游戏后,PAI 会切换到对抗底线的游戏,它可以学会持续地击败这个底线。PAI 随后恢复了信心,在回到更具挑战性的对手面前之前调整了特征。
We used an automated training pipeline. If PAI ever won too few games, PAI’s opponent became adversarial baseline. If PAI ever won too many games, PAI’s opponent adopted its current weights.
我们将拥有 52 个功能的 PAI 用于 5000 款游戏。
Here are the top 15 feature weights after the first 5,000 games of training. There are two key takeaways. 1: Lookahead features make up 8/15 of the top features. 2: Positive features make up 12/15. PAI focuses on getting ahead more than on blocking.
具有 TDL 加权特征的 PAI 可以持续击败业余人类玩家。我们最终将拥有 440 个功能的 PAI 用于另外 5000 款游戏。(这个版本表现稍差,原因我稍后解释。)
搜索模型:探索游戏树
第 6 关:蒙特卡洛树搜索
PAI 的上述版本都依赖于使用评估函数来对当前的一组可能的移动进行评分。但是他们都没有展望未来。
蒙特卡罗树搜索 (MCTS)是一种“向前看”的算法 PAI 没有使用评估函数,而是快速测试尽可能多的场景。它随机地和自己玩成吨成吨的游戏(在我们的例子中:每层 250,000 个游戏)。根据随后的胜率,PAI 可以估计哪些举措会带来更好的结果。
结果并不惊人。Pathwayz 中的随机移动通常很糟糕(例如给你的对手一个永久棋子),所以随机抽样是一个弱预测器。
第七级:Expectimax
用我们的评价函数探索博弈树怎么样?
Expectimax 是一种针对随机对手进行前瞻的算法。对于 PAI 的每个可能的移动,它考虑对手的所有后续移动,应用我们的评估函数,并平均这些值。PAI 选择平均值最高的棋步。因为我们的评估函数需要时间(与模拟随机移动相比),PAI 无法预见未来。特别是在游戏开始的时候,当树的宽度很大的时候,向前看似乎要花很长时间。所以 PAI 只向前看了一层。
请注意,expectimax 针对随机的对手进行优化。像 MCTS 一样,结果并不令人惊讶,因为在 Pathwayz 中随机抽样是一个弱预测器。
第 8 级:极小极大
探索游戏树对抗非随机对手怎么样?
Minimax 是一种预测最有可能的对手的算法。Minimax 的工作方式类似于 expectimax。然而,PAI 并没有对对手可能的移动进行平均,而是假设对手会尽最大努力移动。PAI 最大化其自身移动的评价函数,同时最小化其对手移动的评价函数。
请注意,在面对次优对手时,极小极大并不是最佳玩法。换句话说,它总是从其他玩家那里获得最好的,而不是利用常见的错误。
更迫切的担忧是 minimax(像 expectimax)在 Pathwayz 看不到很远。由于巨大的树宽,尤其是早期游戏,PAI 最初只限于向前看一层。然而,随着游戏的进展,我们确实增加了前瞻深度,所以 PAI minimax 是一个令人生畏的终极玩家。
第 9 级:光束最小值最大值
派还能看得更远吗?
波束极小最大值**是一种通过减少宽度来增加前瞻深度的算法。在每一层,PAI 选择最上面的 X 步,然后只向前看那些步。在随后的关卡中,PAI 再次缩小了宽度— 只看对手的最佳 X 步,然后看自己的最佳 X 步。(在我们的例子中,X 是 5。)
如果最佳长期移动在这些 X 移动之外,那么波束极小最大将错过它。拥有无限的计算能力,minimax 永远是比 beam minimax 更好的玩家。(当对手离胜利只有一步之遥时,我们让 PAI 恢复到更彻底的 minimax 算法。由于广度有限,计算时间在这个阶段仍然是合理的。)然而,在我们有限的时间和计算资源下,beam minimax 允许 PAI 提前多考虑一层,这使它成为一个更优秀的玩家。
第 10 级:缩小光束最小值最大值
我们 PAI 的最终版本使用了窄光束 minimax** 。PAI 模拟自己的前十步棋,然后是对手的前五步棋,然后是自己的最佳棋。这种窄束允许 PAI 在合理的时间内预测三层,同时考虑更大的一组(十个)可能的移动。**
Here is an example of narrowing beam minimax, with breadths of 3, then 2, then 1 at each level.
定量结果
为了比较我们不同 pai 的表现,我们让他们参加了循环赛。这些 pai 在 20 场比赛中互相比赛,总共 720 场比赛。虽然没有已知的第一个或第二个玩家的优势,我们让每个 PAI 玩相同次数的黑白游戏,以确保公平的比赛。请注意,本次锦标赛中的 minimax 和 beam minimax PAIs 使用的是最初手工挑选的特征权重(而不是 TDL 训练的权重)。还要注意,在这种情况下,“波束极小最大”是我们的“窄波束极小最大”算法的简写。
Here are the results of our round-robin tournament. Win rates are down the columns, loss rates across the rows.
TDL 训练的特征对表现有显著的影响。通过 TDL 训练的两个反射模型比对抗基线表现得好得多。拥有 52 个特征的 TDL 设法在我们最初的光束极小值中获得了 60%的胜率;一个反射模型打败了一个对抗性的搜索模型是一个了不起的成就。我们认为具有 440 个特征的 TDL 遭受过度拟合。它对自己和光束 TDL 极小极大进行训练,所以对这些玩家是最佳的,但对其他玩家是次优的。相比之下,具有 52 个特征的 TDL 适应性更强。
窄光束 minimax 赢得了锦标赛。经过 52 个特征训练的版本击败了拥有 440 个特征的对手。我们把这次胜利归功于前者的多才多艺和后者的过度配合。重要的是要记住,20 场比赛容易受到相当大的差异。然而,定量结果符合我们的定性直觉。
最终,将 TDL 特征与前瞻算法相结合,产生了一个聪明的、适应性强的播放器。当我们在一个人工智能海报展览会上展示我们的成果时,我们为第一个击败我们最好的反射 PAI(经过 52 个特征训练的 PAI)的人提供了 50 美元的奖金。派以 33 比 0 击败了人类。诚然,他们都是业余选手,尽管有些人连续玩了多次,有些人有围棋或其他类似棋盘游戏的经验。
Pictured from left to right: Nikhil Prabala, Louis Lafair, and Michael Tucker at the CS 221 Artificial Intelligence — Principle and Techniques poster fair.
The reflex TDL-weighted PAI defeated amateur humans in 33 straight games at the poster fair, played on the pictured iPads. No one won the $50 reward.
最后,我们让我们最好的前瞻 PAI(通过 TDL 训练的具有 52 个特征的窄光束 minimax)与我们更有经验的团队对抗。PAI 以 5 比 0 击败了 Nikhil Prabala,4 比 1 击败了 Michael Tucker,3 比 2 击败了 me。
In 2017, PAI gets added to the timeline of notable game-playing AIs.
拜师学艺
PAI 作为第一个 Pathwayz AI,可以教我们新的 Pathwayz 方法。它的打法有几处让人惊讶。大多数人类玩家都很保守,会等到游戏中期或结束时才翻盘。派,另一方面,是侵略性的,在最初的几个动作中持续翻转。PAI 试图(经常成功地)获得早期领先,这样它就可以在比赛结束时进行进攻而不是防守。
根据定义,反射模式是一个贪婪的玩家,所以它的侵略性是有道理的。
A: Reflex TDL-weighted PAI (black) plays a permanent piece, aggressively flipping at start of game. B: Later in the same game, PAI has now played permanents in four of its first six moves
搜索模型的侵略性更令人惊讶。
PAI beam minimax (black) has learned that getting ahead early is helpful. PAI tends to go on the aggressive.
同样令人惊讶的是 PAI 阻挡空翻的能力。PAI 有时会在非常规空间打球,只是为了阻挡对手的翻转。
PAI (black) strategically places to block a dangerous flip.
PAI 会尽一切可能阻止(或至少延长)对手的胜利,有时甚至会翻转自己的棋子。
A: I (black) am one turn away from winning. B: PAI (white) makes an end-game sacrifice, flipping its own piece to prevent a winning flip next turn.
继续玩和发展 PAI 会揭示更多新颖的策略。
有先发制人的优势吗?
许多双人策略游戏中都存在第一个玩家的优势。在围棋中,第二名棋手获得补偿以消除第一名棋手的优势。在国际象棋中,第一个玩家的胜率在 55% 左右。胜率的计算方法是:(胜+ 1/2 平)/总游戏数。
鉴于历史数据的缺乏,在 Pathwayz 没有已知的第一玩家优势。有些人认为先发制人有优势。
A commenter on BoardGameGeek.com believes there’s a first-player advantage in Pathwayz.
我们决定看看这个理论是否成立,以及在多大程度上成立。在 Pathwayz 白棋先走。我们在 1000 场比赛中发挥了我们最好的反射(因此也是最快的)PAI。白棋赢了 487 场,黑棋赢了 499 场,他们平了 14 场。换句话说,第一个玩家的胜率是 49.4%。如果有的话,似乎有一个次要的第二玩家优势,虽然它可能会随着更多的模拟或更先进的人工智能而减少。黑棋的优势如此之小,以至于我们可以假设(直到一个更大的游戏数据库存在)没有明显的玩家优势。
下一级
我们精心选择的功能,加上前瞻功能,使得顶级玩家的开发变得更加高效。与使用神经网络相比,我们能够用更少的时间(和更少的计算能力)构建、训练和测试 PAI。
对于未来的迭代,我们有几种方法可以继续改进 PAI。第一,我们要训练 PAI 对抗人类玩家;我们已经建立了开始这样做的基础设施。第二,我们想尝试将蒙特卡罗树搜索与我们的评估函数相结合,有效地增加预见性。第三,我们希望试验不同的特性集,在 52 和 440 之间,不要过度拟合。最后,我们希望使用神经网络,允许更复杂、更微妙的非线性策略。
目前的 PAI 是一个强大的玩家,但不是不可阻挡的。随着神经网络的发展,PAI(像 AlphaGo Zero )展示了超人游戏的潜力。
鸣谢
PAI 是许多人投入大量时间和大量帮助的结果。感谢 Percy Liang 和 Stefano Ermon 教授 CS221:人工智能——原理与技术。感谢扎克·巴恩斯的反馈。感谢 Dylan Hunn 提供初始 GUI。感谢一路走来尝试与 PAI 对战的所有人。
当然,非常感谢迈克尔·塔克和 Nikhil Prabala ,他们花费了无数的时间(也失去了太多的睡眠)来共同开发 PAI。这篇博文在很大程度上借鉴了我们为 *CS 221 撰写的最终联合论文**:人工智能——原理和技术。***
基于模式的识别确实对自然语言处理有帮助
Photo: https://pixabay.com/en/stained-glass-spiral-circle-pattern-1181864/
现在大家都在说机器学习和深度学习。当然,我们喜欢深度学习的结果,但它非常渴望数据。那么我们如何建立初始化数据集呢?我们可以在标记数据上分配一组资源,但这非常昂贵。或者,我们总是在初始化阶段使用正则表达式(用于文本)来理解数据,并为验证和启动项目构建手工功能。
正则表达式是软件工程中的一项基本技能。它存在于前端开发(如 Javascript),后端开发(如 Java)。数据科学怎么样?它在自然语言处理领域非常有用,允许我们从非结构化文本中检索大量有价值的信息。
在本文中,我将重点介绍如何在数据探索、预处理和特性工程阶段利用正则表达式,而不是正则表达式实现。看完这篇文章,你会明白:
- 什么是正则表达式?
- 我们如何在数据科学生命周期中利用正则表达式?
- 拿走
什么是正则表达式?
这个概念是由斯蒂芬在 20 世纪 50 年代提出的。正则表达式(也称为 regex 或 regexp)的目标是通过给定的文本(包括字符、符号和数字)来识别期望的模式。你可以访问这里了解详细的历史。
给定一个句子,我们想提取一个日期。如何才能实现这一点?
sentence = "Jul 29 is the 210th day of the year"
pattern = r'((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{2})'results = re.findall(pattern, sentence)
print('Result:', results)
输出
Result: [('Jul 29', 'Jul')]
它支持其他模式,包括“任何数字”,“任何单词字符”。详情可以查看 python 官方页面或者在 google 或 stackoverflow 中搜索。
我们如何在数据科学生命周期中利用正则表达式?
数据探索
当你开始一个项目时,第一步是探索你所拥有的。对于数字特征,我们可以做一些基本的统计计算,如最小值,最大值,平均值。对于分类特征,我们可以数出可能的值和分布。文字呢?除了计算唯一标记(例如单词)和最高频率单词之外,您还想从非结构化文本中探索更多特征。
Photo: https://pixabay.com/en/binoculars-person-telescope-woman-1869456/
- 理解数据:给定一条新闻,你想知道这条新闻是否包含事件的日期,以及显示是否包含日期的分布。如果数据源自带就太好了。但是,大部分时候不会发生。
- 验证假设:在之前的项目中,我们认为产品的重量是一个有价值的信号。然而,我们不知道非结构化文本是否包含这些信息。所以我们用正则表达式来验证它。幸运的是,有明确的格式,如毫克,毫克,克,公斤等。通过使用正则表达式,我们可以很容易地构建一个新的特征。
预处理
我们无法获得清晰的数据或时间(不像 Kaggle 竞争)。所以我们必须清理原始数据。当然,人们相信深度学习可以学习一切并减轻那些无意义的重量。根据我的经验,高质量的数据会带来更好的结果。
Photo: https://pixabay.com/en/divorce-separation-marriage-breakup-619195/
- 通过网络爬虫系统获取数据。它可以是 HTML 格式,我们利用“美丽的 Soap”解析原始数据。我们仍然需要使用正则表达式来清理不需要的数据
- 通过 OCR 获取意外数据。假设原始输入仅为英文,但 OCR 识别出它不正确并引入了非英文字符。它很容易被过滤掉。
特色工程
在确认模式(如日期、体重等)后,我们可以使用正则表达式将它从非结构化数据中挖掘出来。提取正确的“日期”可能是非常具有挑战性的任务。这里有一些例子
- 不同格式:2018 年 1 月 1 日、2018 年 1 月 1 日、2018 年 1 月 1 日等。
- 不同解读:如何解读 08/01/2018?如果你在香港,应该是一月八日,但是在美国是八月一日。
Photo: https://pixabay.com/en/tools-construct-craft-repair-864983/
一般来说,有 3 种可能的情况:
- 定义好的格式:日期、邮件、电话号码都是定义好的格式。只要谷歌一下,就能找到可靠的正则表达式。
- 自定义正则表达式:很多时候,我们需要构建自定义正则表达式。在系统日志分析项目中,目标之一是从日志中提取域密钥(假定域密钥保存在日志文件中)。域密钥可以是系统生成的 15 位数字或数字和字符的组合。
- 无格式:例如,您想要获取产品名称,如 iMac、Goolge Home 等。没有标准格式,所以我们可能无法通过正则表达式提取它。
拿走
同样,如果你在 NLP 领域工作,正则表达式是一个基本的技能。如果你能掌握它,这是一个优势,但至少你必须实现一些简单的正则表达式。正则表达式是一种预定义的规则库提取方法。它:
- 可能需要大量时间来实现一个通用模式。如前所述,正则表达式是一个规则库系统,我们需要一点一点地构建自定义的正则表达式。
- 不支持看不见的模式。它是一个基于规则,并暗示它不能处理新模式。
- 无法保证满足所有场景。即使是电子邮件,我们也不能保证 100%成功。
关于我
我是湾区的数据科学家。专注于数据科学、人工智能,尤其是 NLP 和平台相关领域的最新发展。
模式和反模式
“数据科学”的标志是,它继承了其父母统计学和计算机科学,是一门致力于识别数据中发现的模式的艺术。然而(我很可能在这个人物塑造方面相当不公平,我要提前承认),在我看来,它也是一种艺术,它把它揭示的模式看得太重,低估了它是什么的“艺术”方面的程度。
我们可以从一些看似琐碎的问题开始:什么是“模式”?《牛津英语词典》的第一个定义,“一个重复的装饰性设计”提供了一个有用的起点:我们可能不关心“装饰性设计”的方面,但我们认为某样东西是一种“模式”(即《牛津英语词典》的第三个定义,“一种在某件事情发生或完成的方式中可辨别的规则和可理解的形式或序列”),因为我们看到它“重复”发生但是多少重复算“重复”呢?两次?三次?在这一点上,人类和统计数据有些不一致——但这种不一致比人们想象的要微妙一些。
假设我们看到掷硬币连续三次产生“正面”。关于这枚硬币,它告诉了我们什么,并且暗示了下一次抛硬币的结果?对(一些)人类来说,三个可能就足够了,或者(对其他人来说)不够。经典统计学会给出相反的建议:如果你从假设硬币是公平的开始,你会以 1/2 = 1/8 的概率连续得到 3 个头像,这可能不足以宣布观察到足够的模式。后者通过使用数字和概率逻辑,似乎比前者更符合逻辑和科学:至少,它产生了每个人都能同意的东西,不管他们先前的信仰如何,这是一件好事。但它也滑入了一些非常可疑的东西:这个断言本身,是从一个关于硬币真实性质的先验开始的:硬币一开始就是公平的,除了方便的假设之外,没有其他证据。
换句话说,我们想知道 P(coin|observations)。我们计算了 P(观测值|硬币),我们断言它对 P(硬币|观测值)有一些见解——坦白地说,这是假的。我们仍然不知道我们是否真的看到了一个模式——只是它不是一个模式,如果基于一些我们同意的先验,先验地,是合理的。如果我们拒绝假设的先验事实上是合理的前提,那么观察到的事件没有任何意义,至少在建立是否存在一个模式方面,独立于一个人的主观信念——尽管它确实确定地建立了这一点,但它不是一个模式,前提是真实的 P(头)是 1/2,1/8 不足以建立一个模式。请注意,这重复了最近在某些学术研究领域对 p 值的争议:p 值正是后者。我们有一些理论,X >为 0,如果,事实上,X = 0,我们会看到我们观察到的只有很小的概率。如果我一开始就不相信这个理论,我就没有理由认真对待 p 值。混淆 P(A|B)和 P(B|A)是一个非常常见的统计谬误,我们总是在概率和统计课程中花时间讲述它,但是,通过合适的框架,它显然不是人类一直掌握的东西!
前面的内容暗示了构成“模式”的稍微不同的定义模式是一种重复的现象,它证实了观察者所期望的规律性。对于那些认为硬币以很高的概率“正面”落地的人来说,连续三个正面就足够了。有些矛盾的是,同样的情况也发生在那些认为硬币以很高的概率落在“尾巴”上的人身上,即使是在相反的方向上。对于那些认为硬币总是“反面”落地的人来说,即使是一个“正面”也足以打破这个模式。它当然排除了 P(T) = 9/10 和 P=0.01 足以打破模式的信念——因为 P(HHH|P(T) = 9/10) = 1/1000。
这些都把我们带回了统计学的基本问题:我们想知道真相,却只知道数据;除了我们从数据中推断的以外,我们不知道真相。此外,不管我们喜欢与否,我们从数据中推断出的东西至少部分取决于我们现有的信念。HHH 的观察与许多关于 P(头)的真实值可能是什么以及什么算作“模式”的信念是一致的如果观察仅限于连续三个头,没有人能够说服相信 P(H) = 1 的人相信其他观点——因为这足以符合他对“模式”应该是什么的想法。然而,对于那些相信 P(T)非常高的人来说,这足以打破先验——对他们来说,这是反模式的,与他们预期的想法不相容。换句话说,反模式通过强制改变先验知识来提供信息。模式不会。
那么,当我们(认为我们)通过任何分析方法看到数据中的模式时,我们应该怎么做呢?我们希望至少考虑所有的可能性:如果 P(H) = 1,P(HHH) = 1,如果 P(H) = 0.9,P(HHH) = 0.73,等等。如果我们能合理地给每一个可能的偶然性分配概率:P(P(H)= 1 = 0.01,P(P(H)= 0.9 = 0.05,等等,我们也许能在估算 P(H)方面做得更好。,并计算期望值(例如,德国坦克问题),但做好这项工作可能涉及更多的数据工作,以及获得对产生掷硬币结果的过程的实质性(或特定领域,对你们数据人来说)理解,如果“客户”只是想知道 P(H ),这似乎是不合理的。
当密歇根大学的政治心理学家开始他们关于投票行为的开创性工作时,他们注意到了两种模式(是的,我过于简单化了),他们认为足以引起注意:大多数人投票给政党,农民投票给农业。其中,前者最受关注——我认为它本不该如此。它给出了 P(A|B 或 C 或 D 或 E 或 F) =高的表象,只是因为 F 是 B、C、D、E 和 F 的并集的一个很小的子集。当子集 F 被单独检查时,P(A|F) =低,并且因为差别明显很大。分别考察 P(A|C)或 P(A|E)的概率是怎样的,特别是当子集变小时?(例如 1960 年的天主教徒?或者,如果天主教徒是意大利人,即使在 1960 年——在某些城市,例如纽黑文,他们的政治精英不太喜欢爱尔兰政客——或者来自共产主义的波兰难民——他们可能更喜欢尼克松的反共产主义声誉——缺乏足够的数据使得这种精细的分析在数据收集昂贵的日子里很难——所以我们只能确定一个明显的模式,即使在数据有限的情况下也能显示出来。原则上,大数据允许我们观察隐藏在大数据中的小数据,但前提是我们观察并捕捉到足够多的我们感兴趣的小数据。但是,这样做值得吗?这是一个好问题。
N.N .塔勒布一直试图推广“抗脆弱性”的理念“反模式”思维的概念已经在统计和 cs 人员中流行起来,至少在某些领域是这样。当然,保险行业很久以来就一直在这一理念上开展业务。但是正如我所描述的,他们的工作是基于能够清楚地、量化地识别模式和反模式之间的界限。确信硬币总是正面落地的人没有理由为可能出现的反面做准备。如果没有应急计划,他可能会更“划算”,因为他不会浪费时间为那些永远不会发生的事情做准备。而且,与上面的卡通例子不同,这甚至可能是合理的:如果 P(Head)确实是 0.9999999,为尾部的微小意外做准备的成本可能确实是巨大的,而潜在的收益很小,如果只是因为事件的罕见性——但是你怎么知道 P(Head)是否真的那么大呢?(提示:你不知道,也不能知道——除了你能从数据中推断出来的以外,为此,你需要做出通常不是基于“事实”的假设)罕见灾难的证据往往是灾难本身,越是罕见,它就越不可能被数据捕捉到,即使是大数据,足够清晰地突破稳健性的要求。换句话说,试图产生一种“抗脆弱”算法,就变成了一个保险问题:你是否愿意以增加一层复杂性甚至金钱成本为代价,购买保险来抵御一些看似不寻常的东西?你买了太阳从西边出来的保险吗?对抗俄克拉荷马州的地震?对抗 1929 年 10 月 24 日的股市崩盘?如果你在事情发生前是对的,那么在事情发生后,你会被视为天才。如果没有,你会被视为一个浪费了很多时间来防范不明飞行物的傻瓜。很有可能,你会被视为两者兼而有之——在事情发生前几秒钟还是个傻瓜,几秒钟后又是个天才。
这和数据科学有什么关系?一切。不管理论上的思考如何,实际应用中的数据科学寻求以清晰和可靠的方式识别模式,因此,有一种制度偏见:它建立在噪音是废物,是需要消除的敌人的假设上。数据科学推销自己的前提是,如果你有正确的算法和大量的数据,你可以高度可靠地预测各种各样的事情——不管细微差别,这是“客户”期望数据科学提供的东西,并将在这个前提下运作——数据科学给了他们可以作为“事实”依赖的“好”答案。在一个完美的数据科学宇宙中,一切都是可靠可预测的。没有噪音。另一方面,您购买了针对噪音的保险,针对打破您通常预期的模式的事情,因为您不“知道”这些反模式,即使您“知道”它们会发生。换句话说,因为你知道你不知道——这与数据科学所追求的正好相反。我认为这种状态永远不会达到:现实到数据的转换是不完整的;通过算法识别数据中的模式,尤其是微妙的模式,永远不会是完整的——特别是因为统计方法在设计上偏向于发现大的鲁棒模式,而不是高度条件化的微妙的小模式(人类更擅长后者)。我确实认为,我们可以达成这样的理解,即数据科学乌托邦是不可实现的,甚至在逻辑上是不可能的,无论是简单的方式还是困难的方式:我们可以诚实坦率地对待数据科学的潜在限制,并花时间探索它们——不是作为无条件的限制,而是作为一套工程条件和权衡,它们确实是这样——或者我们可以处理一些巨大的灾难以及由膨胀的期望引起的相关反弹。
意外药物过量死亡的模式
Pixabay
意外阿片类药物过量导致的死亡已经成为美国最严重的公共卫生危机之一。死亡人数令人心痛,全国每个公共卫生机构都在努力扭转这一趋势,因为这种流行病导致的死亡率逐年攀升。
为了发现阿片类药物过量死亡的模式,我开始着手分析 2016-17 年康涅狄格州的意外药物过量死亡的公开数据集。我的目标是深入了解药物滥用受害者的行为,这可能有助于采取预防措施,帮助抗击这种流行病。
关键指标
首先,我对数据进行了探索性分析,以确定将成为分析焦点的关键指标。在对几个不同的参数进行分析后,我能够专注于导致大多数药物过量死亡的共同因素。超过 80%的死亡是由于过量服用多种药物的致命组合而不是一种药物引起的。从这个角度来看,将分析建立在因服用多种药物而导致的死亡上是有意义的,因为它们占了死亡的大多数。
方法论
为了探索药物和死亡之间的关系,我决定对数据集进行社会网络分析。通过改变不同参数生成的社交网络可以相互比较,从而可以识别任何新兴模式。使用统计编程语言 r 中的社会网络分析包进行分析。
分析
1)确定致命的药物组合
由于我们的分析基于药物组合导致的死亡,因此在 2016 年和 2017 年建立了一个药物与死亡相关的网络。本质上,如果可卡因和海洛因是过量致死毒性报告的一部分,那么在这个网络中它们之间就会有联系。为了找出最重要的问题,与导致 100 多人死亡的两种药物的联系用红色标出。
- 我们观察到,大多数用药过量死亡都与芬太尼有关,芬太尼是一种阿片类药物,因此表明阿片类药物危机有多严重。
- 其次,芬太尼和海洛因在这两年都是意外过量死亡人数最多的药物。
- 第三,在比较两个网络中的死亡人数时,涉及芬太尼这种药物的死亡人数在 2017 年大幅上升*。相比之下,涉及芬太尼的死亡人数而非*有所下降,但可卡因和海洛因的死亡人数略有上升。这清楚地表明,2017 年,药物消费模式发生了转变,从其他药物转向芬太尼。
2)药物和人口统计学
为了深入了解 2017 年药物过量伤亡者的人口统计数据,基于药物过量受害者死亡时消耗的数量创建了三个社交网络,分别为两个、三个和四个或更多个。
蓝色和粉红色的节点代表男性和女性死亡,每种颜色的深度随着年龄的增长而增加。此外,绿色节点表示死亡时消耗的药物。绿色节点的大小取决于蓝色或粉色节点的“度数”或链接数量。绿色节点的大小显示了在服用多种药物时导致大多数意外过量死亡的药物。
- 在所有三个网络中,芬太尼在与其他药物结合使用时是导致死亡人数最多的药物。紧随其后的分别是海洛因和可卡因。
- 从视觉上看,很明显,在所有三个网络中,因药物过量导致的意外死亡问题对男性人口的影响不成比例地大于女性人口。由两种、三种和四种或更多种药物引起的女性死亡率分别为 32%、29%和 23.5%。这一趋势表明,男性人口更容易因消费大量药物而成为意外药物过量的受害者。
离别赠言
当我们深入分析时,很容易忽略这样一个事实,即每一个用蓝色或粉色表示的空节点,都曾经是一个宝贵的生命,和我们每个人有着同样的梦想和渴望。仅这一事实就足以让每个参与扭转局面和控制阿片类药物流行的人的努力有了意义。
模式中的模式
我的朋友内森·巴托(Nathan Batto)发表了一篇非常有思想的文章(T1 ),表面上是关于人口密度和政治之间的联系,但实际上,有一点更深入和更广泛的概括:当我们看数据时,根据聚合的程度,我们错过了什么和得到了什么(我想这是和生态推断问题一样的统计问题的一部分)
虽然我们都不是印度政治方面的专家,但他提到的比哈尔邦的例子非常恰当(请注意,这是一个思想实验,而不是真实的东西)。这是一个人口非常密集,但非常“农村”的地方。这不同于其他被归类为“城市”的人口密集的地方:它们的特点是有许多空地和相对较小的人口密集的地方——你有中央公园,那里没有人居住,还有一个小的(就土地面积而言)公寓楼,有数千人居住。换句话说,在非常小的聚合级别上,这是一个非常高方差(但以非常可预测的方式)的数据环境-对于每个给定的平方公里,人口要么是 100,000,要么是 0,或者类似的情况。另一方面,在更“农村”的地区,每平方公里可能是一个“小”村庄的所在地,周围有许多小农场:因此每平方公里可能有 10,000 人口。
人口密度分布的差异反过来又对信息的传播方式产生了有趣的影响,为此,让我们从森林火灾的角度来思考。一个区域着火的概率取决于可燃材料的密度,而它蔓延到起火点以外的概率取决于紧邻周围区域的相同材料的密度,同样,也取决于它们之间的间隙大小。人们可能会假设,给定平方公里的可燃性可能会受到规模收益递减的影响:10 万的密度燃烧的可能性不是 1 万的 10 倍,特别是因为两者都可能首先着火。但是“城市”区域和“农村”区域之间的区别在于,虽然前者表面上可能更密集,但如果从小的聚集来看密度分布,它也具有被防火带包围的非常密集的街区,而后者提供几乎相等密度的连续分布,或至少小得多的防火带(自从我研究这个主题以来已经过了很长时间,但想想金属和其他高导电性材料和半导体的晶体结构的差异——相同的基本逻辑)。农村可能不太容易着火,但它为火势蔓延提供了更便捷的途径,而城市则不然。然而,一个人口非常密集的“农村”地区是另一回事:它可能很容易着火,并让它迅速蔓延。
显然,还有许多其他社会文化因素被这一简单的区别所掩盖。让我们更仔细地思考其中的一些:在什么情况下,什么样的政治/社会/文化信息会在邻居之间传播?班菲尔德和威尔逊早在 20 世纪 50 年代就预言了电视的兴起将终结机器政治,他们给出的解释至今仍是正确的:他们指出(邻居的)口口相传只是传播政治信息的多种沟通方式之一。晚间新闻竞争并削弱了机器组织者挨家挨户传播信息的能力,这种能力要有效得多——假设人们足够信任晚间新闻的话。换句话说,密集而孤立的人口,文化水平低,获取信息技术的机会有限,已经成熟,可以通过类似机器的技术进行动员,这当然是许多社会的政治标志,据我所知,包括印度的大部分农村地区(以及所谓的第一世界的许多地方)。)
因此,如果我们想知道众所周知的森林火灾可能会在哪里发生并危险地蔓延,我们不仅要查看密度(以及其他伴随的解释),还要查看足够精细的聚集水平上的密度分布。高密度区域有许多起作用的因素,但被当地防火隔离带隔离,可能会燃烧,但不会蔓延(这就是为什么城市骚乱从来没有超越骚乱?从字面上看,城市贫民区是被隔离的——通常,附近没有任何地方可以让大火蔓延,即使它被点燃了。)密度较低的地区,其影响因素可能较少,但在聚集程度较低时,密度变化较小,但情况不同。也许火灾发生的概率稍微低一些,但是在爆发的条件下,火灾蔓延的概率可能更高。
那么,我们是想用我们可以可靠预测的无关紧要的火灾来填充我们的数据呢,还是想用不太可靠的方式来预测实际上可能造成破坏的大火呢?
PCA 与自动编码器
Behind Shinjuku Station, Tokyo.
在本教程中,我将解释 PCA 和自动编码器(AE)之间的关系。我假设你对什么是 PCA 和 AE 有一个基本的了解,但是如果你不熟悉 PCA 或自动编码器,请阅读[1,2]。
关于使用线性激活自动编码器(AE)来近似主成分分析(PCA),已经写了很多。从数学的角度来看,最小化 PCA 中的重建误差与 AE [3]相同。
然而,为什么要把自己局限于线性变换呢?神经网络非常灵活,因此我们可以通过使用非线性激活函数引入非线性[4]。此外,随着特征数量的增加,与 AE 相比,PCA 将导致更慢的处理。我们的假设是,声发射跨越的子空间将类似于主成分分析发现的子空间[5]。
在这项研究中,我们将看到 PCA、线性和非线性自动编码器之间的异同。请注意,非线性 AE 将是非线性的,除非输入数据被线性跨越。
首先,让我们加载虹膜数据集,并在[0,1]之间缩放。
让我们创建一个函数,根据原始标签绘制数据。
然后,我们使用数据集来拟合主成分分析,并绘制前两个主成分分析图。我们可以看到出现了两个大斑点,使用目标标签,我们可以看到这三个集群是如何包含在这两个斑点中的。
A plot of PC1 against PC2.
我们的第一个网络是一个线性 AE,有 3 层(编码、隐藏和解码),编码和解码层有“线性激活”,隐藏层有两个神经元。本质上,这种结构通过将隐藏层中的数据从四个特征减少到两个特征来近似 PCA。如您所见,该模型收敛得非常好,我们的验证损失已降至零。在通过隐藏层传递训练数据之后,我们得到两个新的向量,并且通过将它们彼此相对绘制,我们清楚地形成了类似于 PCA 的斑点和集群。
Left: the linear AE train and validation loss. Right: A plot of AE1 against AE2.
让我们创建第二个 AE,这一次我们将用一个 sigmoid 代替两个线性激活函数。这种网络结构可以被认为是具有非线性变换的 PCA,并且类似于上面的网络结构,它收敛到局部最小值,并且我们可以绘制得到的密集向量。
Left: the sigmoid-based AE train and validation loss. Right: A plot of AE1 against AE2.
我们的最后一个 AE 使用了带有 L1 正则化的 relu 激活。换句话说,我们希望通过使用具有受限表示的非线性 AE 来近似 PCA[2]。类似于先前的网络,它收敛到局部最小值,并且两个密集向量显示包含在两个斑点中的三个集群。
Left: the relu-based AE train and validation loss. Right: A plot of AE1 against AE2.
在训练所有 3 个自动编码器并通过隐藏层推送我们的训练数据后,我们比较前 2 个 PC 和 AE 的密集特征。我们可以清楚地看到,在所有模型中,这些向量中的数字并不相同。通过绘制每两个向量并观察得到的集群,这一点非常清楚。接下来,我们将比较具有 2 个和 3 个聚类的简单 KMEANS 如何对数据进行分类。
A comparison of all four plots.
我们想知道,用 KMEANS (k=2 & 3)聚类的每个结果是否可以在所有模型中相似地标记数据,即使密集范围非常不同。以下是训练集中样本的打印分类向量和指标。打印输出和度量显示,使用由不同算法创建的不同密集表示,可以找到两个相同或三个非常相似的集群。我们可以看到,当对两个斑点进行聚类时,度量分数基本相同,并且当对三个已知的花类进行聚类时,度量数字非常接近。请注意,由于神经网络的随机性,可能会出现微小的差异。
我要感谢 Natanel Davidovits 和 Gal Yona 的宝贵评论、校对和评论。
Ori Cohen 在机器学习、脑机接口和神经生物学领域获得了计算机科学博士学位。
参考资料:
[1] 在 keras 建造 AE
[3] CSC 411:第 14 讲:主成分分析&自动编码器,第 16 页。
[4] 深度学习自动编码器教程
[5]PCA 和自动编码器有什么区别
惩罚间接推理
我想告诉大家我们与我的朋友和同事弗朗西斯科·布拉斯克斯合作的新出版物。我们引入一种新的估计方法叫做惩罚间接推理。我将从一个例子开始,耐心等待,开始观想。
Figure 1. Nice weather.
夏日呼吸,海鸥尖叫,夏日阳光灿烂。
假设你正坐在沙滩上,看着大海,这时有一艘船驶过。你看到它的形状和高度,认出旗帜的颜色,观察从船上掀起的波浪。你可以相当肯定地猜测这是一艘渔船还是一艘油轮,以及它来自哪里。
Figure 2. Foggy weather.
现在假设你坐在同一个海滩上,看着大海,但是你看不到大海的远处——有雾——你唯一能看到的是海浪:它们有多大,它们的形状。你注意到从你面前传来的波浪与右边 10 米处的不同,所以你决定有一艘船,但你不知道是哪一艘。如果浪小,你估计应该是渔船而不是油轮。
在第一种情况下,你根据对船的直接观察、形状、颜色和大小做出决定。在第二种情况下,你推断出船看着波浪的信息。这个例子可以帮助我们解释完全信息估计方法和有限信息估计方法的区别。
从呼吸到行动
假设我们抛硬币,想要估计正面的概率,参数 p 完全描述了抽奖的分布。一旦我们观察到多次投掷硬币,我们就可以通过最大化似然函数来估计参数 p。假设我们把硬币抛 10 次,得到 9 个头和 1 条尾巴。如果投掷是独立的,在这种情况下,可能性是观察到 9 个头和 1 个尾巴的概率,数学上是 p⁹ ×(1-p)。使该函数最大化的值 p̂ 是该模型的 p 的最大似然估计。
当我们详细知道参数是如何进入数据的联合概率分布的,并且可以显式地计算出似然函数时,我们就可以通过最大化似然来估计模型的参数。这种方法利用所有的信息,并导致最精确的估计。然而,对于某些模型,很难根据这种直接信息建立估算过程。
例如,这种可能性可能无法通过分析获得。在这种情况下,我们可以采取间接的方式。如果每个参数的选择意味着均值和方差的某个值,我们可以构造一个矩估计的方法。改变对参数的猜测,并选择使观察到的力矩和理论上隐含的力矩之间的差异最小化的值。
间接推断方法可以被视为矩方法的推广,其中我们用来确定它是什么船的波不必是矩,而是任何辅助统计。此外,我们可以模拟给定参数选择的数据,并使用模拟的数据计算辅助统计,而不是从理论上计算矩。好像我们有很多资源,可以建造不同类型的船,让它们在我们身边航行,观察它们产生的波浪!所以,我们有了间接推论。
现在,假设你在一个区域,你知道某些类型的船只不能巡航,要么该区域是军用的,民用船只不能进入,要么海底太近。当你猜测你看到的是哪种类型的船时,你可能会想要使用这个信息。
这是我们论文的核心,我们引入了一个惩罚项,允许研究者对估计结果进行控制。这个想法类似于在使用贝叶斯估计方法时有一个信息丰富的先验。这在估计结构经济模型时特别有用,因为否则,参数估计很难根据基本的经济理论来解释。
我们还建立了估计量在正确和错误模型下的渐近性质,以及在强参数和弱参数识别下的渐近性质。并进行蒙特卡罗研究,揭示罚函数在形成估计量的有限样本分布中的作用。我们强调了在最新动态随机一般均衡模型的实证研究中使用该估计量的优势。
全文链接在这里,感谢我的朋友斯坦尼斯拉夫·达维多夫的插图。如果你有任何问题或想讨论论文,我很乐意讨论!
带有流失预测的人员分析
Pixabay Image
每年都有许多公司雇佣许多员工。公司投入时间和金钱来培训这些员工,不仅如此,公司内部也有针对现有员工的培训计划。这些计划的目的是提高员工的工作效率。但是人力资源分析在这方面有什么用呢?而且仅仅是为了提高员工的业绩吗?
人力资源分析
人力资源分析(HR analytics)是分析领域中的一个领域,指的是将分析过程应用于组织的人力资源部门,以期提高员工绩效,从而获得更好的投资回报。人力资源分析不仅仅是收集员工效率的数据。相反,旨在通过收集数据来提供对每个流程的洞察,然后使用这些数据来做出如何改进这些流程的相关决策。
人力资源流失
人力资源流失是指员工随着时间的推移逐渐流失。一般来说,相对较高的流失率对公司来说是个问题。人力资源专业人员通常在设计公司薪酬计划、工作文化和激励系统方面发挥领导作用,帮助组织留住优秀员工。
自然减员对公司有什么影响?人力资源分析如何帮助分析流失?我们将在这里讨论第一个问题,对于第二个问题,我们将编写代码,并尝试一步一步地理解这个过程。
减员影响公司
高员工流失率的一个主要问题是它给组织带来的成本。职位发布、招聘流程、文书工作和新员工培训是失去员工和替换他们的一些常见费用。此外,定期的员工流动会阻碍您的组织随着时间的推移增加其集体知识库和经验。如果你的业务面向客户,这一点尤其重要,因为客户通常更喜欢与熟悉的人互动。如果不断有新员工加入,就更有可能出现错误和问题。
希望这些基础知识有意义。让我们继续编码,并尝试找出人力资源分析如何帮助理解自然减员。
介绍
为了开始练习,我使用了从 Kaggle 下载的 IBM HR Analytics 员工流失&性能数据集。该数据集包括诸如年龄、员工角色、日薪、工作满意度、在公司的年数、在当前角色的年数等特征。在这个练习中,我们将尝试研究导致员工流失的因素。这是一个由 IBM 数据科学家创建的虚构数据集。
让我们开始工作吧。
数据准备:加载、清理和格式化
#Load the data
hr_data = pd.read_csv("HR-Employee-Attrition.csv")
hr_data.head()
HR Data Snapshot
#Missing values check
hr_data.isnull().sum()
Missing values
幸运的是,我们没有任何缺失值,从上面的 HR 数据快照来看,我们似乎也不需要格式化数据。
数据分析
让我们看一下数据,看看这些特征是如何影响数据和员工流失的。我们需要先检查要素的数据类型,为什么?因为我们只能看到一个数据集中数值/连续值的分布。为了获取分类/对象值的峰值,我们必须将它们与数值变量绑定,然后您将能够看到它们与数据集的相关性,或者您可以用虚拟变量替换分类变量。
#Check the structure of dataset
hr_data.dtypes
Structure of HR Data
在这个练习中,我们的目标是预测员工流失,重要的是要了解哪些变量对流失的影响最大。但在此之前,我们需要知道变量是否相关如果相关,我们可能希望在建模过程中避免这些。
有许多连续变量,我们可以查看它们的分布,并创建一个配对图网格,但这将有太多的代码来查看相关性,因为有许多变量。相反,我们可以创建一个数字变量的 seaborn 热图,并查看相关性。相关性不太差的变量(即相关值趋向于 0),我们将挑选这些变量,并与它们一起前进,将留下强相关的变量(即相关值趋向于 1)。
Correlation Heatmap of HR Data(Numerical variables)
从上面的热图中,我们现在可以看出哪些变量相关性差,哪些变量相关性强。
#Let's remove the strongly correlated variables
hr_data_uc = hr_data_num[['Age','DailyRate','DistanceFromHome',
'EnvironmentSatisfaction', 'HourlyRate',
'JobInvolvement', 'JobLevel',
'JobSatisfaction',
'RelationshipSatisfaction',
'StockOptionLevel',
'TrainingTimesLastYear']].copy()
hr_data_uc.head()
HR_Data_with_UC
停下来想一想,我们这里没有什么,两件事,一,分类变量和任何关于损耗的信息。让我们把这些和上面的数据框架结合起来。
#Copy categorical data
hr_data_cat = hr_data[['Attrition', 'BusinessTravel','Department',
'EducationField','Gender','JobRole',
'MaritalStatus',
'Over18', 'OverTime']].copy()
hr_data_cat.head()
HR Categorical variable
我们先用 1 和 0 代替减员中的是和否。
Num_val = {'Yes':1, 'No':0}
hr_data_cat['Attrition'] = hr_data_cat["Attrition"].apply(lambda x: Num_val[x])
hr_data_cat.head()
HR Categorical Data
现在用虚拟值替换其他分类变量。
hr_data_cat = pd.get_dummies(hr_data_cat)
hr_data_cat.head()
HR Categorical Data
现在我们已经有了数字格式的所有数据,我们现在可以组合 hr_data_num 和 hr_data_cat。
hr_data_final = pd.concat([hr_data_num, hr_data_cat], axis=1)
hr_data_final.head()
Final HR Data
数据建模
我们有了最终的数据集。我们现在必须开始建模——预测损耗。等等?你是不是也和我一样迷茫?我们已经有了流失数据,那么还能预测什么呢?在回归和分类问题中,大多数情况下,您使用可用值运行模型,并通过比较观察值和真实值来检查模型的准确性等指标。如果你没有真值,你怎么知道预测是正确的。现在你会意识到,训练数据阶段是多么重要。我们以一种可以预测(几乎)正确结果的方式训练该模型。
在这个数据集中,我们没有任何缺失的损耗值,我们将把数据分为训练和测试。我们将根据训练数据训练模型,并根据测试数据预测结果。
在这个特定的练习中,我们将使用随机森林分类器。在开始编写代码之前,让我们先了解一下 RF 分类器的背景。
随机森林分类器
弱估计量的个数组合起来形成强估计量。
随机森林的工作原理是装袋。这是决策树的集合。bagging 方法用于通过组合弱模型来增加总体结果。它是如何组合结果的?在分类问题的情况下,它采用在装袋过程中预测的类的模式。
我敢打赌,你们很多人一定在想,为什么我们选择随机森林来解决这个特殊的问题?为了回答这个问题,首先让我们来写代码。
模型建立
from sklearn.cross_validation import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_scoretarget = hr_data_final['Attrition']
features = hr_data_final.drop('Attrition', axis = 1)#create the train/test split
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.4, random_state=10)#Create the model and train
model = RandomForestClassifier()
model.fit(X_train,y_train)#predict the results for test
test_pred = model.predict(X_test)#test the accuracy
accuracy_score(y_test, test_pred)
该模型给出了 0.84 的准确度分数,不算太差。即使使用默认参数,随机森林也能很好地工作。这是我们在这个问题上使用射频的原因之一。尽管这可以通过调整随机森林分类器的超参数来改善。随机森林也不容易过拟合,因为它具有随机性。
随机森林模型的一个最好的特征是——它提供了数据/模型中变量/特征的重要性。对于这个人力资源分析问题,我们想知道哪个特性/因素对流失的影响最大,RF 的 one function 可以为我们提供这些信息。这是我们使用射频的另一个原因。
feat_importances = pd.Series(model.feature_importances_, index=features.columns)
feat_importances = feat_importances.nlargest(20)
feat_importances.plot(kind='barh')
看来月收入对员工流失的贡献最大。
摘要
在这篇文章中,我们看到了数据在人力资源部门的重要性(实际上在大多数地方都很重要)。我们看到了如何避免使用相关值,以及为什么在建模时不使用相关值很重要。我们使用了随机森林,并了解了它如何优于其他可用的机器学习算法。最重要的是,我们发现了对员工最重要的因素,如果不满足这些因素,可能会导致员工流失。
参考
人们在谈论“再现性危机”和“p 值”,我觉得我应该理解,但我的眼睛已经呆滞了…你能帮忙吗?
我不知道回答这样的问题有多大的市场,但如果有人知道,也许我会成为数据科学领域的“亲爱的艾比”。(“亲爱的斯塔西”有些潜力。)
I’m excited to use this GIF.
撇开蹩脚的笑话不谈,让我们来谈谈这个“复制危机”或“复制危机”这是什么,我为什么要关心?
在许多科学进步中,验证研究结果的过程通常来自所谓的“同行评审过程”。
比方说,我已经开发了一种治疗关节炎的药物,我相信这种药物优于目前医生使用的药物。我召集了一群人,给他们中的一些人服用“马特的药物”,给他们中的一些人服用“目前的药物”,然后跟踪观察关节炎的影响在“马特的药物”组和“目前的药物”组之间是如何变化的。如果“马特的药物”组似乎关节炎疼痛较少,我可能会尝试公布我的结果,让每个人都知道我的药物对人们更好。
但是等等。也许你患有关节炎,或者你的伴侣或父母或朋友患有关节炎。你没有理由相信我的发现。事实上,你很可能会怀疑。你只想给你的家人和朋友最好的,但我可能想通过出售我的药物发财…所以你怎么知道我所做的是为了你和你家人的最大利益?
这就是“同行评审过程”发挥作用的地方。如果我想在医学杂志上发表我的结果,会有真正的裁判阅读我的作品,以确保我遵循了最佳实践。
Who knew that academics could be so… sporty?
这些裁判可能会问这样的问题:
- 作者在这里是否有任何可能影响他/她写文章的利益冲突?
- 这个实验的设计有意义吗,或者看起来这个实验不是以一种低于标准的方式设计的?
- 分析是公平和平衡的吗,或者结果是经过精心挑选的,有利于特定的结果?
在一篇文章被“审阅”(并且可能被修改)足够多次之后,一篇文章可能会被发表,这样每个人现在都可以阅读它。读者可以继续“非正式地评审”这些文章,这样如果一篇不太完美的文章通过了评审过程,原始文章的作者或发表文章的期刊可以修改文章或完全编辑它。根据不同的杂志,真正阅读一篇文章的人数通常很少。
从理论上讲,如果我发表了结论,认为“马特的药物”明显优于现有的药物,你应该能够对一组类似的患者进行相同的研究,并得到类似的结果。(在统计学术语中,我们指的是来自同一“抽样人群的患者。))
术语“再现性危机”指的是这样一个事实,当我们试图再现实验时,我们经常观察不到相同的结果。
This is a problem.
如果我用相同的设置运行两个实验,并得出两个不同的结论…这是非常有问题的。基本上,发生了两件事之一:
- 我们最初的实验结论是错误的。
- 我们重复实验的结论是错误的。
因为除了参与者的选择之外,没有任何东西可以区分这两个实验,所以不可能确定上面两件事情中的哪一件确实发生了。
如果一个效应不能被复制,我们现在不得不质疑我们最初的结论是否合理。如果我们不能相信最初的结论,那么那个结论和任何基于那个结论的研究现在都成了问题。
等等。所以…
没错。不幸的是,同行评议的期刊充满了我们不希望复制的结果。
那么我们能做什么呢?
嗯,我们想找到一些方法来阻止这场危机。有许多建议的方法来防止这种情况发生,但是一个非常公开的建议涉及…p-值。
WOO!
呃。马特。
我知道,我知道。跟着我。
那么,什么是 p 值呢?
超级正式地…如果我们运行一个实验,那么一个p-值是这样的概率,如果零假设为真,我们重新运行我们的实验,我们得到一个与我们在最初的实验中观察到的一样极端或者更极端的测试统计。
嗯。你能让它不那么正式吗?
假设我说,平均来说,我一周吃四个墨西哥卷饼。你觉得那是 荒谬可笑还有各种各样的事情 但是你连续十周跟着我,跟踪我吃的东西。比方说,在这十个星期里,我平均每周吃 3.5 个墨西哥卷饼。
p-值是我们说的一种方式,假设我说我平均每周吃四个墨西哥卷饼是真的,你观察了我十周,发现我平均只吃了 3.5 个卷饼,这有多极端?P 值允许我们量化我们观察到的结果(平均 3.5 个墨西哥卷饼)与我最初声称的结果(平均 4 个墨西哥卷饼)的差异。
Me eating an average of four Chipotle burritos per week is, sadly, not unrealistic.
非正式的呢?
p-数值简单地衡量实验结果的极端程度。
- 大的 p 值意味着我们的实验结果符合我们的预期。
- 小的 p 值意味着我们的实验结果与我们的预期大相径庭。
小 p 值用来表示实验结果在统计上是显著的,这是一个神奇的术语,我们用它来表示我们有足够的证据来对我们试图研究的东西做出一些结论。
你说的“小 p 值”或“大 p 值”是什么意思
嗯,社会需要某种方式来决定“这里有足够的证据来断定我们 相信 为真的东西实际上是真的吗?”人类非常擅长遵循明确的规则,但不太擅长仅凭“感觉”做决定。
正因为如此,我们历史上有一些阈值来规定“是的,我们的p*-值足够小,可以得出结论”或“不,我们的p-值太大,不能得出这些结论。”过去,我们使用 5%作为阈值。有些字段使用不同的值,我们可以深入兔子洞讨论p——黑客和多重测试,但这超出了本文的范围。*
The p-value was popularized by Ronald Fisher.
罗纳德·费雪,“现代统计学之父”(如果我的硕士论文导师算作我的学术母亲,也是我的学术高曾祖父),推广了p*-值和“显著性”的 5%阈值*
- 你运行一个实验,得到 4%的 p 值。你的结果很好,你想要的结论是正确的!你要出版了!
- 你做了一个实验,得到了 7%的 p 值。你的结果并不重要,你想要的结论一定是错的。
所以,听起来不错。有什么问题?
Cristina Yang of Grey’s Anatomy fame is always right.
嗯,两件事:
- 首先,由于p-值如何表现,如果我们使用 5%作为这个阈值,我可以预期我将在 20 次中检测到一些重要的结果,而实际上并不是一个重要的结果。因此,如果每年有 5000 项外科研究应该有不重要的发现,并使用这 5%的阈值,我预计其中 250 项将被错误地解释为重要。如果我明天就要去做手术——无论是作为病人还是医生——这些听起来都不像是惊人的几率。**
- 第二,p-值不能测量正确的东西。
等等。什么?!我不得不记住一些统计课的荒谬定义。我一直坚持看完这篇文章。你说费希尔是现代统计学之父“他是你奇怪的学术亲戚,他使用 p 值。你说 p 值没有测量正确的东西是什么意思?
回到我之前的墨西哥卷饼的例子,我告诉你我平均每周吃 4 个墨西哥卷饼。然后你为我写了十周的食物日记,发现我每周平均吃 3.5 个墨西哥卷饼。假设我平均每周吃 4 个卷饼,p 值量化了你碰巧看到我吃 3.5 个卷饼的可能性。
也就是说,“假设我实际上每周平均吃 4 个卷饼,你看到我在十周内平均吃 3.5 个卷饼的概率是多少?”
相反,知道这一点不是很好吗:“假设你看到我在十周内平均吃 3.5 个卷饼,那么我实际上平均每周吃 4 个卷饼的概率是多少?”
Mind blown.
看,我们认为p*-值实际上类似于我们最初的结论是正确的概率,假设我们观察了一些真实世界的数据。但是p-值实际上与观察到这个真实世界数据的概率有关,假设我们最初的假设是正确的。*
有 很多 的概率我现在隐瞒,但是 TL;DR 版本是,p-值近似 A 假设 B 为真的概率,当我们真的要评估 B 假设 A 为真的概率时。
p——价值观并不能衡量我们认为是什么。事物p-值测量和我们想要测量的事物 彼此相关 ,但它们并不相同。
Yeah, I know, there’s only one Lindsay Lohan, but Parent Trap >> Full House.
好吧。所以这似乎有点道理。那么,我们如何改变现状呢?
有很多关于如何做到这一点的建议。维基百科对改变事物的五种不同方法有一个坚实、深入的描述。
但是最近流行的一个建议是将统计显著性的标准阈值从 5%移动到 0.05%。如果我们这样做,我们将大大减少假阳性的数量。(使用上面的外科手术示例,我们预计在 5000 项研究中只会看到 2.5 项假阳性,而不是我们之前预计的 250 项假阳性。)
但是,等等,我想你说过 p 值并不能衡量我们想要它衡量的东西。
没错。没错。这改变不了事实。这是治标不治本。
有一大群非常非常聪明的人在推广这个想法。这些作者还认识到,研究人员希望使用不基于 p 值的方法(如贝叶斯因子),包括许多作者自己!作者只是认为,为那些确实依赖于 p 值的人调整这个 p 值阈值,将对这种“再现性危机”产生实质性的积极影响。
因此,周围的“假新闻”和“替代事实”会更少!
否。“假新闻”和“替代事实”是人们不想面对真实事实时使用的这些荒谬的政治传播术语。我们需要尽快将它们从我们的词汇中剔除。
I really dislike Kellyanne Conway. This is not an alternative fact.
但是解决再现性危机确实保护我们不依赖于 实际上 事实上不正确的结论。
你可以在这里 查看我的其他博文 。感谢阅读!
感知器学习算法:其工作原理的图形解释
这篇文章将讨论著名的*感知机学习算法,*最初由 Frank Rosenblatt 于 1943 年提出,后来由 Minsky 和 Papert 于 1969 年完善并仔细分析。这是我之前关于麦卡洛克-皮茨神经元模型和感知器模型的帖子的后续。
引用注:本文的概念、内容和结构完全基于 IIT 马德拉斯大学教授的Mitesh Khapra讲座幻灯片和视频 CS7015:深度学习 。
感知器
你可以浏览我之前关于感知器模型的帖子(上面有链接),但我假设你不会。因此,感知器不是我们今天在人工神经网络或任何深度学习网络中使用的 Sigmoid 神经元。
感知器模型是比麦卡洛克-皮茨神经元更通用的计算模型。它接受一个输入,对其进行聚合(加权和),只有当聚合和大于某个阈值时才返回 1,否则返回 0。如上所示重写阈值,并使其成为具有可变权重的常量输入,我们最终会得到如下结果:
单个感知器只能用于实现线性可分的功能。它接受实数和布尔输入,并将一组权重与它们相关联,同时还有一个偏差**(我上面提到的阈值)。我们学习权重,我们得到函数。让我们用一个感知器来学习一个 OR 函数。**
或使用感知器的功能
上面所发生的是,我们基于不同输入集的 or 函数输出定义了几个条件(当输出为 1 时,加权和必须大于或等于 0),我们基于这些条件求解权重,并且我们得到了一条完美地将正输入与负输入分开的线。
没有任何意义吗?也许现在是你浏览我所说的那篇文章的时候了。Minsky 和 Papert 还提出了一种使用一组示例(数据)学习这些权重的更具原则性的方法。请注意,这不是一个乙状结肠神经元,我们不会做任何梯度下降。
热身——线性代数基础
矢量
向量可以用多种方式定义。对物理学家来说,矢量是位于空间任何地方的任何东西,有大小和方向。对于一个 CS 爱好者来说,向量只是一个用来存储一些数据的数据结构——整数、字符串等等。在本教程中,我希望你用数学家的方式想象一个矢量,其中矢量是一个箭头,它的尾部在原点,在空间中延伸。这不是描述向量的最好的数学方法,但是只要你有直觉,你就可以做得很好。
注:以下截图是我从3 blue 1 brown的视频上借来的Vectors。如果你还不知道他,请查看他的系列文章微积分 。当谈到数学可视化时,他简直不可思议。**
矢量表示法
二维向量可以在 2D 平面上表示如下:
Source: 3Blue1Brown’s video on Vectors**
将这一思想推进到 3 维,我们在 3 维空间中得到如下箭头:
Source: 3Blue1Brown’s video on Vectors**
两个向量的点积
以使本教程更加枯燥为代价,让我们看看什么是点积。假设你有两个向量 oh size n+1 、 w 和 x ,这些向量的点积( w.x )可以计算如下:
The transpose is just to write it in a matrix multiplication form.
在这里, w 和 x 只是一个 n+1 维空间中的两个孤独的箭头(直观地说,它们的点积量化了一个向量向另一个向量的方向移动了多少)。所以从技术上讲,感知器只是在计算一个蹩脚的点积(在检查它是大于还是小于 0 之前)。感知器给出的区分正例与反例的判定边界线实际上就是 w . x = 0。
两个向量之间的角度
现在,如果你知道向量之间的角度和它们各自的大小,同样的点积可以用不同的方法计算。方法如下:
反过来,你可以得到两个向量之间的角度,只要你知道向量,只要你知道如何计算向量的大小和它们的点积。
当我说 w 和 x 的夹角余弦为 0 时,你看到了什么?我看到箭头 w 垂直于箭头 x 在一个 n+1 维空间(说实话在 2 维空间)。所以基本上,当两个向量的点积为 0 时,它们是互相垂直的。
设置问题
我们将使用一个感知器来估计我是否会根据上述输入的历史数据来观看电影。数据有正反两个例子,正的是我看的电影即 1。基于这些数据,我们将使用感知器学习算法来学习权重。为了视觉上的简单,我们将只假设二维输入。
感知机学习算法
我们的目标是找到能够完美分类我们数据中的正输入和负输入的 w 向量。我将直接进入算法。这是:
我们用一些随机向量初始化 w 。然后我们迭代数据中的所有例子,( P U N )包括正面和负面的例子。现在,如果一个输入 x 属于 P ,理想情况下,点积 w.x 应该是多少?我会说大于或等于 0,因为这是我们的感知机在一天结束时唯一想要的,所以让我们给它。而如果 x 属于 N ,那么点积必须小于 0。因此,如果您查看 while 循环中的 if 条件:
情况 1: 当 x 属于 P 及其点积w . x0
情况 2: 当 x 属于 N 及其点积 w.x ≥ 0
只有在这些情况下,我们才更新随机初始化的 w 。否则,我们根本不接触 w ,因为案例 1 和案例 2 违反了感知器的规则。所以我们在例 1 中把 x 加到 w (咳咳矢量相加咳咳),在例 2 中把 w 减去 x 。
为什么指定的更新规则会起作用?
但是为什么会这样呢?如果你已经明白了为什么会这样,你就明白了我这篇文章的全部主旨,现在你可以继续你的生活了,谢谢你的阅读,再见。但是如果你不确定为什么这些看似任意的 xx和 w 的运算会帮助你学习到完美的 w 可以完美的分类 P 和 N ,请继续使用我的方法。
我们已经建立了当 x 属于 P 时,我们要w . x0,基本感知器法则。我们这样说也是指当 x 属于 P 时, w 与 x 之间的角度应大于 90 度。填空。
答: w 和 x 之间的角度应该小于 90°,因为角度的余弦与点积成正比。
所以不管 w 向量可能是什么,只要它与正例数据向量( x E P )的角度小于 90 度,与负例数据向量( x E N )的角度大于 90 度,我们就没事了。理想情况下,它应该是这样的:
x_0 is always 1 so we ignore it for now.
所以我们现在强烈认为当 x 属于 P 类时 w 和 x 之间的角度应该小于 90°,当 x 属于 N 类时它们之间的角度应该大于 90°。停下来,说服你自己,上面的陈述是真实的,你确实相信它们。这就是为什么这一更新有效的原因:
Now this is slightly inaccurate but it is okay to get the intuition.
因此,当我们将 x 与 w 相加时,当 x 属于 P 并且w . x0(情况 1)时,我们实际上是增加 cos(alpha)** 值,这意味着减少 alpha 值,即 w 与 x 、之间的角度类似的直觉也适用于 x 属于 N 且 w.x ≥ 0 的情况(情况 2)。**
这里有一个玩具模拟,模拟了我们最终可能如何学习正面例子中小于 90 度的角度和负面例子中大于 90 度的角度。
We start with a random vector w.
收敛性的证明
现在,你没有理由相信这一定会收敛于所有类型的数据集。看起来可能会有这样的情况,w 继续四处移动,永远不会收敛。但是人们已经证明了这个算法是收敛的。我附上哥伦比亚大学的迈克尔·柯林斯教授的证明— 在这里找到论文。
结论
在这篇文章中,我们快速地看了一下什么是感知机。然后我们用线性代数的一些基础知识热身。然后我们看了一下感知器学习算法,然后继续想象它为什么工作,即如何学习适当的权重。
感谢你阅读这篇文章。自己活也让别人活!
答
Photo by Roman Mager on Unsplash