天蓝色的海底总动员
使用 Azure 自定义视觉服务探索对象识别
Marlin and Dory are on a search for Nemo — Marlin’s only son, who was taken by a fishing trawler. I just figured out a way to utilize the power of Azure cloud to help them find Nemo in the vast sea.
我正在浏览我童年时收藏的珍贵电影,这时我发现了《海底总动员》。小时候,我一直想帮助尼莫回到他爸爸身边。我只是不知道怎么做。
现在我在技术的陪伴下成长,我想我终于找到了出路。
我们可以用 Azure 帮助马林找到尼莫。这是我们需要的:
什么是 Azure 自定义视觉?
Azure Custom Vision 是一项认知服务,让我们可以构建、部署和改进我们自己的图像分类器和对象检测器。图像分类器是一种人工智能服务,它根据图像的视觉特征将标签应用于图像,而对象检测器是一种人工智能服务,它在图像中找到特定的对象,在我们的情况下,就是 Nemo。
自定义视觉服务使用机器学习算法将标签应用于图像。图像在提交时会被贴上标签。然后,该算法根据这些数据进行训练,并通过在这些相同的图像上测试自己来计算自己的准确性。一旦算法经过训练,我们就可以测试、重新训练,并最终根据我们应用程序的需求使用它来对新图像进行分类。
创建 Azure 自定义 Vision 帐户
要开始构建我们的人工智能解决方案,首先我们需要获得一个 Azure 自定义视觉帐户。让我们开始吧:
Azure Custom Vision Portal
2.登录与 Azure 订阅关联的 Microsoft 帐户,并接受服务条款
Terms of Service
建立一个识别尼莫的模型
现在,我们都准备建立一个机器学习模型,它将能够识别尼莫,并告诉他在哪里。要使用自定义视觉服务,我们需要在 Azure 门户网站中创建自定义视觉训练和预测资源。这将创建训练和预测资源。
创建新项目
- 要创建您的第一个项目,选择新建项目。将出现创建新项目对话框。
Azure Custom Vision projects
2.输入项目的名称和描述。然后选择一个资源组。如果您的登录帐户与 Azure 帐户相关联,资源组下拉列表将显示您的所有 Azure 资源组,其中包括自定义 Vision 服务资源。
3.从项目类型中选择对象检测,并从域中选择常规(精简)。紧凑域针对移动设备上的实时对象检测的约束进行了优化。紧凑域生成的模型可以导出到本地运行。
**分类和对象检测:**自定义视觉功能可分为两个特征。图像分类将一个或多个标签应用于图像。对象检测与此类似,但它也返回图像中可以找到应用标签的坐标。从而使我们能够在图像中找到物体的位置。
4.最后,选择创建项目。
选择训练图像
作为最低要求,Azure Custom Vision service 要求每个标签至少有 15 张图像,以便在初始训练集中进行对象识别。我们还需要一些额外的图像来测试模型一旦训练。
为了有效地训练模型,我们将使用具有视觉多样性的图像,即尼莫的不同图像。
此外,所有训练图像必须满足以下标准:
- 。jpg,。png,或者。bmp 格式
- 大小不超过 6MB(预测图像为 4MB)
- 最短边不少于 256 像素;任何短于此长度的图像将被自定义视觉服务自动放大
上传和标记图像
在本节中,我们将上传并手动标记图像,以帮助训练对象检测器。
- 要添加图像,请单击添加图像按钮,然后选择浏览本地文件。选择打开以上传图像。
2.与分类器不同,在使用 Nemo 选择图像区域后,必须手动标记单个图像。单击上传的图像以打开标记窗口。
训练模型
一旦所有的图像都被标记,我们需要训练模型使用它。开始训练
- 选择训练按钮。对象识别服务使用所有当前图像来创建识别 Nemo 的视觉质量的模型。
Training the model
2.选择 1 小时培训预算的高级培训,点击培训。
Selecting Training Type
训练过程应该只需要几分钟。在此期间,有关培训过程的信息显示在 Performance 选项卡中。
评估模型
训练完成后,评估并显示模型的性能。定制视觉服务使用我们提交的用于训练的图像来计算精确度和召回率,使用一种称为 k-fold 交叉验证的过程。精确度和召回率是模型有效性的两种不同的度量:
- 精度表示正确的已识别图像的比例。例如,如果模型将 100 幅图像识别为 Nemo,并且其中 99 幅图像实际上是 Nemo,那么精度将是 99%。
- 回忆表示被正确识别的实际图像的比例。例如,如果实际上有 100 个尼莫的图像,并且模型识别出 80 个是尼莫,则召回率将是 80%。
平均精度(mAP): mAP 告知海底总动员处物体探测器的整体精度。
注 :紧凑域比一般域精度低,无法构建可在计算资源有限的智能手机上本地运行的模型。
Evaluating the model
概率阈值
这是在计算精度和召回率时被认为是正确的预测概率的阈值。
用高概率阈值解释预测调用倾向于以回忆为代价返回高精度的结果,这意味着发现的检测是正确的,但是许多甚至没有被模型发现;低概率阈值则相反,即模型检测到大多数实例,但在该集合中存在假阳性。考虑到这一点,我们应该设置概率阈值,这样才能找到尼莫。
测试我们的模型
- 选择顶部菜单栏右侧的快速测试。此操作会打开一个标记为快速测试的窗口。
2.在快速测试窗口中,我们点击提交图像字段,并输入要测试的图像的 URL。要使用本地存储的图像,我们点击浏览本地文件按钮并选择一个本地图像文件。
Quick Test window
所选图像出现在页面中间。然后结果以两列表格的形式出现在图像下方,分别标记为标签和置信度。查看结果后,我们可以关闭快速测试窗口。
Results in a Quick Test window
使用预测图像进行训练
我们可以使用之前提交的图像进行培训,具体步骤如下:
- 打开自定义视觉网页并选择预测选项卡查看提交给物体检测器的图像。
2.将鼠标悬停在图像上以查看模型预测的标签。要将图像添加到训练数据,选择图像,选择图像中正确的标记区域,然后选择窗口右上角的 X 按钮。图像从预测中移除并添加到训练图像中。可以通过选择训练图像选项卡进行查看。
我们现在可以用更新的训练数据重新训练模型,它将被保存为一个新的迭代。
最后,我们已经使用 Azure 自定义视觉服务构建、训练和测试了一个对象检测模型,现在可以帮助 Marlin 寻找 Nemo。
下一步是什么?
我们将导出训练好的模型,使用 CoreML 在 iPhone 上的应用程序上本地运行
感谢阅读。如果你喜欢它,有什么要分享的或者有什么问题,请在评论中告诉我。
在 R 中寻找 R
相关系数®、回归的相关系数®和编程语言 R 都是数据科学的三个标志性 R。相关系数®是一个非常著名的统计测试,在机器学习模型中非常常用。回归的相关系数可以确定斜率与相关值的一致程度,是回归目标预测的重要指标。r 是一种函数式语言,它植根于语言 S,是数据科学家非常流行的选择。
是的,我们在 R 中找到 R,因为我觉得这很有趣。
r 是一种函数式语言,其历史和计算一样悠久。虽然这有时是一种优势,但 R 也一直是一种较小的语言。重要的是要记住,尽管 R 对于数据科学家来说的确是一件大事,并且是一门伟大的语言;r 将不会像 Python 或 C 语言那样拥有广泛的支持。当然,Python 和 C 也有其局限性,Julia 和 Scala 也是如此。总的来说,虽然我发现自己使用 R 的次数比 Python、Julia 甚至 Scala 少得多,但我仍然认为 R 是一个很棒的工具,我甚至比现在更想使用它。
为什么?
我喜欢低级的数据科学,也喜欢高级的。令人惊讶的是,经常会遇到那些对相关系数、模型等非常熟悉的人。在更大的范围内。但是了解一个模型的内部运作,或者至少了解一个模型如何与统计数据一起工作来证明一个更准确的结果是有价值的信息。
在不使用库的情况下设计自己的算法肯定会让你在面试中成为众矢之的,并让你在你的领域中与众不同。这是因为为了开发复杂的算法,可能需要大量的计算机科学技能。我不想详细说明,但是 CS 技能经常会成为工作中非常重要的一部分。
除了正当理由,它的乐趣!
函数公式
formula for the correlation coefficient
在首先,这个计算似乎相当全面和困难。当游戏中的每个部分都被分解成小块时,这个公式就变得简单多了。
- n —样本量。
- σxy——x 和 y 的点积之和。
- σx——x 的总和。
- σy——y 的总和。
- σx——x 的点平方之和。
- (σx)—x 的二次幂之和。
- σy——y 的点平方之和。
- (σy)—y 的二次幂之和。
分解方程后,显然我们需要计算每个单独的部分。幸运的是,R 是在考虑线性代数的情况下构建的。R 中几乎所有的操作数都可以用于线性代数。
- n —我们可以使用 R 的基函数“length”来计算 n
- xy——我们首先需要 x 和 y 的乘积,幸运的是,我们可以使用*操作数来乘以数组。
- σ()基本适马可以使用 sum()函数来计算。
n = length(x)
xy = x * y
Σx = sum(x)
Σy = sum(y)
Σxy = sum(xy)
很简单,对吧?
现在我们只缺少 x 和 y 的点指数的和,以及 x 的平方的和。
x 的和与 x 的和……
x2 = x ^ 2
y2 = y ^ 2
sx2 = sum(x2)
sy2 = sum(y2)
注意σx 和(σx)之间的区别,它们可能读起来一样,但是它们不是同一个。
编写我们的函数
结合上面所有的数学,我们可以得到我们的值,然后把它们代入 R 函数的公式中,就像这样:
correlationcoeff <- function(x,y){
n = length(x)
yl = length(y)
xy = x * y
sx = sum(x)
sy = sum(y)
sxy = sum(xy)
x2 = x ^ 2
y2 = y ^ 2
sx2 = sum(x2)
sy2 = sum(y2)
r = ((n*sxy) - (sx * sy)) / (sqrt((((n*sx2)-(sx^2)) * ((n*sy2)-(sy^2)))))
return(r)
}
不用说,挺酷的!但是让我们更进一步,多做一点,只是说我们做到了。
相关系数…的平方
回归的相关系数(相关系数的平方)也是一个非常酷的概念,您应该非常熟悉。r 分数因其用作连续模型回归度量而众所周知。r 通常以百分比形式计算,表示回归相似性或差异(对于机器学习来说,与训练集相似是件好事。)
好消息是,我们可以循环使用之前的函数,以获得与平方®的相关系数
r2 <- function(x,y){
r = correlationcoeff(x,y)
r2 = r ^ 2
return(r2)
}
简单,简单,柠檬榨汁机
创建回归模型
使用我们用于特征选择的相关系数(r ),以及我们的相关决定系数(r ),我们现在可以着手编写一些重要的加权推理模型。让我们使用线性最小二乘回归方程!如果再正式一点的话,我们会创建一个可以放入预测函数的构造函数,这通常是处理模型时的一个好习惯,因为它们被视为对象类型,而不是一个单一的数学函数(可能是因为拟合变换)。)幸运的是,这个函数不是我的作品集,也不是任何形式的专利,所以我们只是将 x 和 y 放入参数中!
pred_linearleastsquare **<-** **function**(x,y,xt){*# Summatation of x*y*xy **=** x ***** ysxy **=** sum(xy)*# N*n **=** length(x)*# Summatation of x^2*x2 **=** x **^** 2sx2 **=** sum(x2)*# Summatation of x and y*sx **=** sum(x)sy **=** sum(y)*# Calculate the slope:*slope **=** ((n*****sxy) **-** (sx ***** sy)) **/** ((n ***** sx2) **-** (sx)**^**2)*# Calculate the y intercept*b **=** (sy **-** (slope*****sx)) **/** n*# Empty prediction list:*y_pred **=** c()**for** (i **in** xt){pred **=** (slope*****i)**+**by_pred **=** append(y_pred,pred)}return(y_pred)}
让我们做一些数据
显然,随机数据并不代表真实世界的情况,但是不必篡改数据清理来测试功能无疑是避免使用真实世界数据的一个可行的借口。
使用我们的功能
首先,让我们从预测线性最小二乘函数开始:
y_pred = pred_linearleastsquare(trainX,trainy,testX)
然后我们可以使用 r2 来验证我们的模型做得有多好。
但是 r 呢?
相关系数可以非常有效地提供两个不同特征之间的相关性信息,或者更好地提供特征和目标之间的相关性信息。我确实在条件模型中看到了很多 r 的使用,尤其是那些关于连续目标的模型。
所以,不用说,R,R 和 R 都是统计学和数据科学中非常酷和重要的概念。期待看到未来几年 R 的位置,以及其他语言的位置。
用 Q-学习算法寻找最短路径
图是用于模拟对象之间成对关系的数学结构。图是由边连接的顶点组成的。在无向图中,我会找到两个顶点之间的最短路径。
Q-learning 是一种无模型强化学习算法。Q-learning 的目标是学习一个策略,它告诉代理在什么情况下采取什么行动。它不需要一个环境模型,它可以处理随机转移和奖励的问题,而不需要适应。
首先,我导入必要的模块。我将使用 networkx 库来定义一个图形
让我们定义并形象化这个图表
Q-learning 算法包括一个代理、一组状态和每个状态的一组动作。它在某种程度上使用 Q 值和随机性来决定采取何种行动。q 值根据所采取行动的回报和可能的未来回报进行初始化和更新。
数学上,
q 值可通过下式计算:
我想找到从 0 到 10 的最短路径。我需要吸引走到涉及 10 的边,因此我给这些行动很高的奖励。在 networkx 库中,G[node]给出了与该节点形成一条边的所有节点。
- 这里我初始化奖励和 Q 矩阵:
- 除了到达节点 10 的动作,我把所有的奖励都设为 0。这些动作是从 8 到 10 或者从 9 到 10。像奖励一样,Q 值在矩阵中初始化。为了消除不可能的动作,他们的 Q 值被设置为-100。例如,在图中,不可能直接从 2 到 10,因此其 Q 值设置为-100。可能的操作被初始化为 0。让我们来看看 R 和 Q 矩阵:
- 现在我将定义一个函数,它接受一个起始节点并返回下一个节点。它也接受随机探索的探索率。否则,它根据可能动作的最高 Q 值来选择动作。
- 之后,我们需要一个函数来更新所采取行动的 Q 值
- 现在是时候提高 Q 值了,从随机节点开始,走 50000 步
让我们检查一下最终的 Q 值
现在,我们可以通过在决定行动时从 Q 矩阵中选择最高 Q 值,找到 0 到 10 之间的最短路径:
参考
**
https://en.wikipedia.org/wiki/Graph_theory
https://everything.explained.today/Q-learning/
http://first time programmer . blogspot . com/2016/09/getting-ai-smarter-with-q-learning . html
After, we need a function for updating Q-value of the action taken
Now it is time to improve Q-values by starting at random nodes and making 50000 walk
Let’s check the final Q-values
Now we can find shortest path between 0 and 10, by choosing highest Q-value from Q matrix when deciding an action:
References
https://en.wikipedia.org/wiki/Graph_theory
https://everything.explained.today/Q-learning/
http://firsttimeprogrammer.blogspot.com/2016/09/getting-ai-smarter-with-q-learning.html
使用深度学习和位置敏感哈希查找相似图像
使用 FastAI & Pytorch 通过 ResNet 34 的图像嵌入找到相似图像的简单演练。也在巨大的图像嵌入集合中进行快速语义相似性搜索。
Fina output with similar images given an Input image in Caltech 101
在这篇文章中,我们试图实现上述结果,即给定一幅图像,我们应该能够从 Caltech-101 数据库中找到相似的图像。这篇文章用一个端到端的过程来指导我如何构建它。复制项目的全部代码都在我的 GitHub 库中。实现上述结果的过程可以分为以下几个步骤-
- 使用 FastAI 和 Pytorch 从 ResNet-34 模型(在 ImageNet 上训练)转移学习以检测 Caltech-101 数据集中的 101 个类。
- 从已训练的 ResNet 34 模型中取倒数第二个完全连接的层的输出,以得到所有 9,144 个 Caltech-101 图像的嵌入。
- 使用位置敏感散列来为我们的图像嵌入创建 LSH 散列,这使得快速近似最近邻搜索成为可能
- 然后给定一个图像,我们可以使用我们训练的模型将其转换为图像嵌入,然后在 Caltech-101 数据集上使用近似最近邻搜索相似的图像。
第 1 部分—数据理解和迁移学习
正如我上面提到的,对于这个项目,我的目标是查询任何给定的图像,并在 Caltech-101 数据库中找到语义相似的图像。该数据库包含 9,144 幅图像,分为 101 个类别。每个类别大约有 50-800 张图片。
Image examples from Caltech-101 database
我们项目的第一个练习是获得一个深度学习网络,它可以准确地对这些类别进行分类。对于此任务,我们将使用预训练的 ResNet 34 网络,该网络在 ImageNet 数据库上进行训练,并使用 Pytorch 1.0 和 FastAI 库对 Caltech-101 数据库的 101 个类别进行分类。正如我在我的前一篇博客中所写的,我将在这篇博客中概述这个过程。你可以参考这本笔记本找到代码做同样的事情。找到下面的步骤做迁移学习分类加州理工学院-101 图像-
- 使用 FastAI 库使用 Pytorch 的数据集加载器加载数据
- 采用预训练的网络,在这种情况下,ResNet 34,并删除其最后完全连接的层
- 在网络末端添加新的完全连接的层,并仅使用 Caltech-101 图像训练这些层,同时保持所有其他层冻结
- 通过解冻所有层来训练整个网络
第 2 部分—使用 Pytorch 钩子提取图像嵌入
现在我们有了一个预先训练好的网络,我们需要从这个网络中为我们所有的 Caltech-101 图像提取嵌入。嵌入只不过是一个对象在 N 维向量中的表示。在这种情况下,图像嵌入是图像在 N 维中的表示。基本思想是给定图像与另一图像越接近,它们的嵌入在空间维度上也将是相似和接近的。
Image embedding visualization. Credit — Blog
你可以在上面这张取自博客的图片中看到,图像嵌入是一种矢量化形式的图像空间表示,其中相似的图像在空间维度上也很接近。
我们可以从 ResNet-34 获得图像嵌入,方法是取其倒数第二个全连接层的输出,该层的维数为 512。为了在 Pytorch 的深度学习模型中保存中间计算以供检查,或者在我们的情况下提取嵌入,我们使用 Pytorch 钩子。钩子有两种类型——向前和向后。前向钩子用于保存在网络中向前传递的信息以进行推断,而后向钩子用于在反向传播期间收集关于梯度的信息。在我们的例子中,我们需要在推理阶段输出倒数第二个完全连接的层,这意味着我们需要使用一个前向钩子。让我们来看看创建钩子的代码(也在我的笔记本的“提取特征”部分)
**class** **SaveFeatures**():
features=**None**
**def** __init__(self, m):
self.hook = m.register_forward_hook(self.hook_fn)
self.features = **None**
**def** hook_fn(self, module, input, output):
out = output.detach().cpu().numpy()
**if** isinstance(self.features, type(**None**)):
self.features = out
**else**:
self.features = np.row_stack((self.features, out))
**def** remove(self):
self.hook.remove()
创建 Pytorch 钩子只需要上面的代码。SaveFeatures 类从的 torch.nn 模块调用 register_forward_hook 函数,并给定任何模型层,它将把中间计算保存在 numpy 数组中,该数组可以使用 SaveFeatures.features 函数检索。让我们看看使用这个类的代码—
*## Output before the last FC layer*
sf = SaveFeatures(learn.model[1][5])*## By running this feature vectors would be saved in sf variable initated above*
_= learn.get_preds(data.train_ds)
_= learn.get_preds(DatasetType.Valid)## Converting in a dictionary of {img_path:featurevector}
img_path = [str(x) **for** x **in** (list(data.train_ds.items)+list(data.valid_ds.items))]
feature_dict = dict(zip(img_path,sf.features))
第 1–2 行:调用类 SaveFeatures,使用模型层引用将倒数第二个全连接层的输出作为输入。
第 4–6 行:传递 Caltech-101 数据以获得他们的预测。请注意,我们对保存预测不感兴趣,这就是我们使用“_”的原因在这种情况下,倒数第二层的中间输出保存在名为“sf”的变量中,该变量是 SaveFeatures 类的一个实例。
第 8–10 行:创建一个 python 字典,其中图像路径是键,图像嵌入是值。
现在我们的字典中有了 Caltech-101 中每个图像的嵌入表示。
第 3 部分—快速近似最近邻搜索的位置敏感散列法
我们可以使用我们新生成的加州理工学院 101 图像嵌入,并获得一个新的图像,将其转换为嵌入,以计算新图像和所有加州理工学院 101 数据库的距离 b/w,以找到类似的图像。这个过程本质上是计算昂贵的,并且作为新的图像嵌入,必须与加州理工学院 101 数据库中的所有 9K+图像嵌入进行比较,以找到最相似的图像(最近邻),这在计算复杂度符号中是 O(N)问题,并且随着图像数量的增加,将花费指数更多的时间来检索相似的图像。
为了解决这个问题,我们将使用位置敏感哈希(LSH ),这是一种近似的最近邻算法,可以将计算复杂度降低到 O(log N)。这篇博客从时间复杂度和实现方面很详细地解释了 LSH。简而言之,LSH 为图像嵌入生成一个哈希值,同时牢记数据的空间性;特别是;高维度相似的数据项将有更大的机会接收到相同的哈希值。
下面是 LSH 如何转换大小为 K-的散列中的嵌入的步骤
- 在嵌入维中生成 K 个随机超平面
- 检查特定嵌入是在超平面之上还是之下,并指定 1/0
- 对每个 K 超平面执行步骤 2,以获得哈希值
the hash value of the orange dot is 101 because it: 1) above the purple hyperplane; 2) below the blue hyperplane; 3) above the yellow hyperplane. Image Credit — Link
现在让我们看看 LSH 将如何执行人工神经网络查询。给定一个新的图像嵌入,我们将使用 LSH 为给定的图像创建一个散列,然后比较来自共享相同散列值的 Caltech-101 数据集的图片的图像嵌入的距离。以这种方式,代替在整个 Caltech-101 数据库上进行相似性搜索,我们将仅对与输入图像共享相同散列值的图像子集进行相似性搜索。对于我们的项目,我们使用 lshash3 包进行近似最近邻搜索。让我们看看做同样事情的代码(你可以在我的笔记本的“使用位置敏感散列法查找相似图片”部分找到代码)
**from** **lshash** **import** LSHash
k = 10 *# hash size*
L = 5 *# number of tables*
d = 512 *# Dimension of Feature vector*
lsh = LSHash(hash_size=k, input_dim=d, num_hashtables=L)*# LSH on all the images*
**for** img_path, vec **in** tqdm_notebook(feature_dict.items()):
lsh.index(vec.flatten(), extra_data=img_path)
上面的代码获取图像嵌入字典,并将其转换为 LSH 表。要查询 LSH 表,我们可以使用下面的代码—
# query a vector q_vec
response = lsh.query(q_vec, num_results= 5)
第 4 部分—将所有内容放在一起
现在我们已经创建了 LSH 表,让我们编写一个脚本,它可以将图像 URL 作为输入,并从加州理工 101 数据库中给我们 N 个(用户定义的)相似图像。这部分的代码在我的 Github 这里。
Process flow of the find similar image script.
该脚本执行以下任务-
- 加载 LSH 表和我们的 ResNet 34 模型(load_model 函数)
- 从用户调用中获取图像 URL 并下载图像(download_img_from_url 函数)
- 从 ResNet-34 传递图像以获得 512 维图像嵌入(image_to_vec 函数)
- 用 LSH 表查询,找到 N 个(自定义)相似图像及其路径(get_similar_images 函数)
- 在所需的输出路径返回输出,也可以使用 Open CV (get_similar_images 函数)显示输出
我们可以在各种应用程序中使用类似的概念,如在我们的照片库中查找类似的图像,相似外观项目的项目-项目推荐,对图像进行网络搜索,查找近似重复的图像等。
总结(TL;博士)。
在博客中,我们看到了深度学习在寻找语义相似图像中的应用,以及如何使用位置敏感哈希(LSH)进行近似最近邻查询,以加快大型数据集的查询时间。此外,值得注意的是,我们不是在原始特征(图像)上使用 LSH,而是在嵌入上,这有助于在庞大的集合中进行快速相似性搜索。
我希望你喜欢阅读,并随时使用我在 Github 上的代码来为你的目的进行测试。此外,如果对代码或博客文章有任何反馈,请随时联系 LinkedIn 或发电子邮件给我,地址是 aayushmnit@gmail.com。您也可以在 Medium 和 Github 上关注我,了解我将来可能会写的博客文章和探索项目代码。
用凝聚聚类法寻找“最佳”最差电影
This is a man entirely composed of veins. From The Room (2003).
首先,一点背景。
当我的父母终于有了有线电视,这对我来说是一件大事。一个我从来不知道存在的奇怪表演的整个世界展开了。我最喜欢的节目(除了《辛普森一家》之外)是一部低成本的连续剧,讲述了一个男人和两个机器人被困在太空,被迫观看可怕的电影。它被称为神秘科学剧场 3000 。因为《T4 》,我这个年纪的一代孩子突然发现了故意看那些糟糕透顶的电影的价值。二十年过去了,我仍然热爱它(鉴于 MST3K 的重启——我并不孤单)。
不过,一些糟糕的电影,就其糟糕程度而言,更纯粹一点。他们的坏是如此纯粹,以至于他们自己变得令人愉快。尽管 Ringer 在几年前提出了相当好的标准,但识别“好”的坏电影与仅仅是坏电影的区别仍然是一个短暂的过程:
1.电影的乐趣必须来自于它的糟糕。它的坏需要是创造一种迷惑的享受感的东西。
2.肯定有一种普遍的感觉,那就是那些制作这部电影的人认为他们所做的很棒,或者至少是好的。好的坏的电影都有最低限度的自我意识。这里有两个例子可以帮助解释这种情绪:(1) 马克格鲁伯不是一部好的坏电影,它是对好的坏电影的致敬;(2)快速五部不是一部好的坏电影,它是一部故意陷入荒谬的电影(然后制造一种类似于好的坏电影自然引发的反应)。
3.这部电影发行时一定是一个严重的失败。评论家们,上帝保佑他们,将电影作为一种艺术形式保持高标准,通常不会因为电影质量差而奖励它。这样,他们是一个有用的,尽可能客观的资源来决定哪些电影是坏的,因此有资格成为好的坏的。
就像我说的,很好,但更多的是直觉。我想知道是否有一种方法可以定量识别这些电影。我觉得有!看看下面的用户评分分布。
一、好电影,《教父》第二部(1974) :
评论分布非常左倾。大部分评分在 4 到 5 之间。
现在吉利(2003) ,一部没有人真正喜欢的电影:
非常右偏,大多是 0.5 和 1.0 的评分。
下面是邻居 (2014) :
一个相当正常的分布。对大多数人来说,这似乎是一部相当不错的电影。
但是现在,*《房间》(2003),*成了衡量其他“好”坏电影的基准:
嗯,有些事。最高收视率在 0.5 桶,第二收视率在 5.0 桶。所以也许我们要找的分布是重尾分布。然而,从上述分布来看,0.5 和 1.0 的评级仍然比 4.5 和 5.0 的评级多得多。因此,我们可能要寻找的是一个高收视率到低收视率的比例高于一般烂片的分布。让我们做一些特征,看看我们是否能抓住它。
首先,让我们看看我们是否能测量一部给定电影向高或低评级倾斜的程度。使用 MovieLens 大型数据集,我创建了一个新的数据帧:
现在,首先我需要创建一个特征来测量高评分或低评分分布的偏斜程度。首先,我们将找到 0.5/1 或 4.5/5 的评论百分比。
df_master[‘percent_0.5to1’] = (df_master.iloc[:,[5,6]].sum(axis=1)) / df_master[‘total_count’]
df_master[‘percent_4.5to5’] = (df_master.iloc[:,[13,14]].sum(axis=1)) / df_master[‘total_count’]
那我们就简单的取高低评级百分比的差。这将告诉我们分布是更倾向于低评级还是更高评级:
df_master['percent_polarity'] = df_master['percent_0.5to1'] - df_master['percent_4.5to5']
为了确保我们选择了那些尾部很重的电影,我们将增加一个功能:
df_master[‘total_tails’] = df_master[‘percent_0.5to1’] + df_master[‘percent_4.5to5’]
现在我们需要考虑样本大小。就目前情况来看,一部有 1000 条评论的电影和一部有两条评论的电影一样有可能出现在我们的最终名单上。我们需要建立一个最低数量的审查。但是我们如何得到这个数字呢?
我决定找出一些评论数量较高的电影的平均值,并计算在达到平均值之前需要多少评论。下面是一些简单的代码:
import statistics as st
from statistics import mean
def rolling_mean(column):
mean = round(column.mean(),2)
roll = []
for i in column:
roll.append(i)
new_mean = round(st.mean(roll),2)
if mean == new_mean:
return mean, len(roll)
else:
continue
最后,我将对这些值进行平均,以确定我的临界值。最后,我的最低评论数是 25 条。我们准备聚类吧!:
df_trim = df_master.loc[df_master.total_count >= 25]
cols = ['percent_polarity','total_tails']
lg_test = df_trim[cols]scaler = StandardScaler()
bigtest_scaled = scaler.fit_transform(lg_test)
bigtest_features = pd.DataFrame(bigtest_scaled, columns=lg_test.columns)
好吧!现在是有趣的部分——凝聚聚类(不,我是认真的)!聚集聚类的神奇之处在于它考虑了数据点之间的个体关系。我已经编写了几个函数来完成这项工作。
首先,我们需要计算出正确的链接和距离参数,看看我们应该创建多少个集群:
def agg_cluster(df,linkage=’ward’,distance=’euclidean’):
# Takes in a dataframe, cluster linkage and distance
# produces a dendogram + cophenet score
Z = shc.linkage(df,linkage,distance)
# Check cophenet correlation score.
c, coph_dists = cophenet(Z, pdist(df))
print(‘Cophenet Correlation:’,c)
plt.title(‘Hierarchical Clustering Dendrogram (truncated)’)
plt.xlabel(‘No. of clusters’)
plt.ylabel(‘distance’)
dendrogram(
Z,
truncate_mode=’lastp’, # show only the last p merged clusters
p=12, # show only the last p merged clusters
leaf_rotation=90.,
leaf_font_size=12.,
show_contracted=True, # to get a distribution impression in truncated branches
)
plt.show()
#Shows the distance of the 12 final splits.
print(‘Last 12 cluster distances:’,Z[-12:,2])
然后,我们将实际执行群集本身,看看我们会得到什么:
def agg_clust(df,n,affinity,linkage):
agg_clust = AgglomerativeClustering(n_clusters=n,affinity=affinity,linkage=linkage)
assigned_clust = agg_clust.fit_predict(df)
return agg_clust,assigned_clust
我最好的选择是平均连锁和欧几里德距离。这是树突图:
注意最后一次合并的距离有很大的跳跃。这表明这些集群并不特别相似。看起来我们最终应该有三个集群。让我们运行它,并将我们分配的群集添加到数据框中:
bigtest_agg,as_bigtest = tqdm(agg_clust(bigtest_features,n=3,affinity=’euclidean’,linkage=’average’))
bigtest_avg = df_trim.copy()
bigtest_avg[‘Cluster’] = as_bigtest
现在,让我们来看看我们的好/坏集群。我们有房间吗?我们有。:
我们还有有肌肉的圣诞老人,歌舞女郎,巨魔 2 ,麦克和我,来自外太空的计划 9,妖精 4:在太空等等。看起来相当不错!
发现单词的复杂性
认识一个单词需要多少个单词?
假设我们想玩一个拼字游戏的升级版,我们仍然根据单词的复杂程度得分,但使用不同的度量标准。我们将根据单词理解的难易程度来评分,而不是使用字母。
这是我们衡量难度的方法。我们会拿一本梅里亚姆-韦伯斯特词典来查这个单词。为了学习一个英语单词,我读了它的定义。至此,我已经明白了原词的意思。然而,要完全理解一个词,我需要知道每个词在其定义中的含义。我需要对每个定义都这样做。考虑下面的例子。
假设我用单词大学开始玩拼字游戏。Meriam-Webster 将大学定义为(这听起来开始像是告别演说……)提供教学和研究设施并被授权授予学位的高等教育机构。对于这个游戏,假设我们已经学会了英语中所有的停用词。这些词像 an、of、for、your、only、to 等。至此,我知道了大学这个词的定义。但我仍然需要了解以下单词的含义:机构、高等、学习、提供、设施、教学、研究、授权、授予、学术和学位。所以,我们看着一个机构的定义,我们学习这个定义中的所有词汇,我们继续前进。
这个游戏有点难以捉摸。不知道院校是什么意思,怎么学大学这个词?没有太大意义。但是我们需要这个警告来防止无限循环。
举个例子,假设我想学习单词数字。它的定义是单位之和。过一会儿,我需要看一下一个单位的定义,它是第一个也是最少的自然数。同样,过一会儿,我需要学习数字的含义。数字是单位的总和。一个单位是最小的自然数。一个数是一个单位。一个单位就是一个数字。我们创造了一个无限循环,所以我们什么也学不到!
我也认识到这是一种简化的方法。例如,假设我想从事计算机科学,所以我想查找单词 Python。我们只考虑第一个定义,即各种大型蛇类中的任何一种。下面的代码是用 Python 写的,但不是用大蟒蛇写的。然而,我们利用这种情况来减少计算的复杂性。
该算法
Meriam Webster 有一个 API——一个应用程序编程接口。把它想象成一个在线词典。我可以告诉我的电脑向梅里亚姆和韦伯斯特询问定义,他们会把它送回来。我们将不得不清理和格式化它一点,但之后,我们将在我们的道路上。
我们需要记录三套。第一个是一组停用词(像 a、and、for、that 等词。从上面)。第二个是已知单词集——这些单词的定义我们已经查过了。我们最后一组是一组不认识的单词。当我们得到一个定义,我们可以看看每个单词。如果是停用词,我们已经知道了。如果是我们之前查过的词,太好了!我们知道定义,可以继续前进。然而,如果我们遇到一个不认识的单词,我们就必须把它添加到不认识的单词集中,以后再看(也许现在,也许以后)。
对于任何有兴趣看代码的人来说,在这里查看一下。
结果呢
我试用的第一个词是 python。为了理解单词 python 的定义,我们需要学习 11227 个其他单词!下面是我们代码的一些输出。
------------------
Definition of python: any of various large constricting snakes
Learnign the word: large
------------------
Definition of large: exceeding most other things of like kind especially in quantity or size
Learnign the word: like
------------------
Definition of like: to feel attraction toward or take pleasure in
Learnign the word: kind
------------------
Definition of kind: a group united by common traits or interests
Learnign the word: group
------------------
Definition of group: two or more figures forming a complete unit in a composition
Learnign the word: two
------------------
Definition of two: being one more than one in number
Learnign the word: feel
下图显示了每 500 次迭代中未知单词的数量。在未知单词数量的图表中,我们看到开始时有一个尖峰。经过大约 25,000 次迭代,对于任何一个任意的定义,我们知道的定义中的单词比不知道的要多。这种模式继续下去,我们慢慢地缩小未知单词的范围。
我们需要学习 11227 个其他单词来理解 python 。但是,要理解傻的定义,只需要 4999 个字。博士取 11221 字。而电脑取 11202。我相对随意地选择了这些单词,所以其中三个需要大约 11,210 个单词似乎有点可疑。尤其是因为梅里亚姆·韦伯斯特在他们的字典中列出了超过 171,476 个单词。
如果您想进一步研究这个问题,或者您想研究一下代码,请在这里查看资源库。你需要一个来自 Meriam-Webster 的 API 密匙,但是你可以在这里很容易地得到一个。寻找快乐!
感谢您的阅读!
疑问?评论?发邮件给我andrew.oliver.medium@gmail.com。我很想收到你的来信!
寻找 NBA 最好(和最差)的合同
Image by Keith Allison on Wikimedia Commons
多年来,NBA 有一些臭名昭著的糟糕合同(也有一些抢断)。像任何理性的人一样,我想做的只是对这些合同进行统计分析。所以在这篇文章中,我有几个目标:
- 确定好合同和坏合同的分界线(并根据球员的工资找到合理的期望值)
- 分析历史上一些最臭名昭著的合同,并确定哪个合同表现最差
- 分析一些当今 NBA 最大的合同,看看谁表现不佳,谁表现过度
如果你想看到的只是结果,那么直接跳到靠近底部的“结论”部分。
方法学
韵律学
方法非常简单。我需要一种精确的方法来测量:
- 工资水平(根据不同年份进行调整)
- 玩家的净贡献
我决定衡量合同的大小,用它在合同期限内占工资帽的百分比来衡量。这似乎是最明显的选择,因为球队主要受到工资帽的限制,因此这反映了球员所占用的相对资源。
第二个指标更复杂。没有一个数字可以完美地概括一个球员在球场上的贡献,但我必须选择一个尽可能好的数字。最终,出于几个不同的原因,我选择了 Win Shares。赢股衡量进攻和防守,它说明了玩家的总影响。例如,如果一名球员错过了时间(由于受伤、停赛或教练的选择),统计数据应该反映他们的整体影响已经减弱。
样品
接下来,我必须确定我要分析的样本。我首先提出了一些选择样本的客观标准:
- 只有完成的合同才应该被衡量,因为合同的签署是基于球员在整个交易期间的预期价值。例如,一个 30 岁的人签了一份 5 年的合同,平均来说,他的收入会逐年下降。因此,这份合同在头一两年可能看起来很划算,但那只是因为预计他们在接下来的时间里表现会差得多。
- 没有入门级的合同将被分析,因为他们设定的工资往往是一个重大的讨价还价。我们将能够得出的一个有趣的结论是,在给定的工资水平下,球员的合理期望——但入门级别的交易可以解释这一点。
- 不会对一年期交易进行分析。这有两个原因。首先,一年期合约是为了低风险而设计的。一份“糟糕”的一年期合同其实并没有那么糟糕。其次,样本量太不稳定了。因伤错过几场比赛很容易让交易看起来很糟糕,一场强有力的比赛会让合同看起来像是一生的交易。
考虑到这些标准,是时候找到并过滤我的样本了。尽管添加每份合同很诱人,但数据的性质意味着需要大量的手动电子表格工作。因此,我不得不挑选一个更小的样本来代表整体。为此,我使用了 Spotrac 并按照 2014 年的工资进行了排序(因为这些合同现在应该已经完成了)。我进入了每个球员(按降序排列),并添加了他们职业生涯中符合上述标准的所有合同。我总共手工记录了不到 150 个不同玩家的 250 份合同。此时,我认为我的样本已经足够大了。
回归
从这里开始,这是一个简单的问题,根据球员的表现回归合同的大小。
图一。赢股与使用的薪金上限百分比的回归
薪资与绩效关系的 R 为 0.324。因此,签署好的合同对通用汽车来说有点冒险。
表 1。数据集统计学。
我们还可以看到,两个数据集的范围相当大。
表二。数据集的取值范围。
在样本中,从技术上讲,最有价值的合同来自布兰登·巴斯。巴斯从 2007 年到 2009 年以不到 160 万美元的价格与达拉斯小牛队签订了一份为期 2 年的合同,并在此期间贡献了大约 840 万股 Win 股票。大多数最划算的合同都是类似的低薪交易。如果我们排除这些低薪合同(低于工资帽的 5%),最有价值的合同是斯蒂芬·库里 2013-2017 年 4400 万美元的合同。在合同期内,他贡献了约 59.6 Win 股份,并获得了 2 次 MVP。按照工资帽的百分比,库里每个赛季贡献 0.97 份 Win 股份(这一指标将被称为“调整后的 Win 股份”或“调整后的 WS ”)。
样本中价值最差的合同是科比的最后一份合同。布莱恩特签署了一份 2014-2016 年的两年合同,他获得了 4850 万美元的薪酬,同时贡献了约 0.2 Win 的股份。科比每个赛季贡献大约-0.003 的调整后赢球份额。
将这种方法应用到 NBA 历史上最糟糕的合同中
现在,当我们用同样的方法量化 NBA 历史上一些最糟糕的合同时。
表 3。选择历史合同的估价。
使用这种方法在一些选择的最高(或接近最高)合同中,吉尔伯特·阿里纳斯 6 年 1.11 亿美元的合同看起来可能是 NBA 有史以来最糟糕的合同。然而,布兰顿·罗伊的交易并不遥远。
将该方法应用于当前玩家
为了应用当前的方法,有几个重要的考虑因素。首先,我们需要考虑基于玩家年龄的性能期望的重要性。
按年龄预测绩效趋势
为了预测这一点,我需要确定当以 Win 份额衡量时,典型的职业轨迹是什么样的。为了衡量这一点,我从 1990 年以来入选 NBA 最佳阵容,但现在已经退役的球员中抽取了一小部分样本(n=50)。我使用了全明星球员,因为我们将分析的大多数现役球员都是全明星球员。
图二。50 名 NBA 全明星球员按年龄划分的平均胜率。
这些数据几乎完全符合你的预期;球员稳步提高,直到 27 岁达到顶峰,从那时起开始永久下降。职业生涯确实有一些不寻常的起伏,这可能是由于样本有限,但由于时间限制,我决定不进一步扩大我的样本。
使用这个指数,我可以确定对球员表现的期望,同时考虑到在合同的剩余时间里他们的表现是否会下降或提高。
为了说明这一点,我将以拉塞尔·维斯特布鲁克为例。威斯布鲁克是他合同的第一年,目前已经 30 岁了,所以他的表现应该会在合同的剩余时间里下降。因此,如果威斯布鲁克要达成“中值交易”(MVD),他将不得不在交易的前几年超额完成交易,以弥补后几年的表现不佳。
从我们之前的数据集,我们知道调整后的年度 WS 0.327 构成了 MVD(在这种情况下使用中值来排除低价值交易中的高价值异常值)。因此,威斯布鲁克实现 MVD 的期望如下:
表 4。调整后的 WS 对拉塞尔·维斯特布鲁克的期望示例。
利用这些值,我们可以将他目前的实际表现与预期表现进行比较。今年,威斯布鲁克有望以约 0.175 的调整后 WS 结束——显著低于 MVD。
样品
为了确定应该分析什么样的合同,我们只需按降序使用最大的活跃合同。
表 4。所有现役 NBA 合同达到或超过 1 亿美元。
样本中不包括合同直到 2019 年才生效的球员,以及处于交易第一年的球员,因为这些球员太不稳定(勒布朗·詹姆斯的交易到目前为止看起来很糟糕,因为他因伤错过了很多时间)。
创建价值类别
图三。调整后 WS 的 250 个合约的直方图。
接下来,为了便于阅读,我想创建一些任意的类别,用于放置球员合同的价值。这些类别如下(根据 MVD 的期望值衡量):
垃圾箱火灾 : -0.24 以下
低值:-0.16–0.24
低于平均水平:-0.08–0.16
公允价值 : +/- 0.08
高于平均水平:+0.08–0.16
高值:+0.16–0.24
偷 : +0.24 以上
评估合同
对这些合同进行评估和分类后,结果如下:
表 5。符合选择标准的价值超过 1 亿美元的有效 NBA 合同的估计值。
请记住,这些代表了团队到目前为止从合同中获得的“价值”——它们并没有预测未来的价值。同样,这里也有一些有趣的结果。按照这个标准,斯蒂芬·库里显然没有履行他的合同。这可能反映了 Win Shares 的缺陷,以及一些伤病的结果。
结果
所以,总结一下这个分析的结果:
- 历史上最差的合同可能是吉尔伯特·阿里纳斯,他签了一份 6 年 1.11 亿美元的合同,但总共只贡献了 2.4 胜股份
- 我能找到的价值最差的合同是科比·布莱恩特的最后一份合同,他在两个赛季中被支付了 4850 万美元,贡献了-0.2 胜股份
- 我发现的最好的合同,排除低价值交易(
- 在价值超过 1 亿美元的有效合同中(不包括第一年的球员),詹尼斯·阿特托孔波提供了最佳价值,而戈登·海沃德提供了最差价值
如何为神经网络找到正确的架构并微调超参数
Image by Gordon Johnson from Pixabay
这篇博文是我上一篇博文的续篇,讲述了如何使用 LSTMs 来预测一只股票的股价,给出它的历史数据。我们已经看到了如何编译一个 Keras LSTM 模型。在这里,我们将看到找到正确架构和超参数的一些方法。
以下是我们的一些选择:
- 手动调谐或手动搜索 —这是找到正确配置的最痛苦的方法,其中你逐个尝试每个参数的具体值。但是有了一些经验,对最初结果的仔细分析和直觉,它可能真的会有帮助。
- 网格搜索——这确实是唯一能从所有选项中给出最佳参数集的方法。您为想要优化的每个参数传递一系列值,然后训练并查找每个组合的验证损失。可以想象,这是最耗时的方法,而且通常不可行。
- 随机搜索 —这是网格搜索的子集,随机选择所有可能组合的子集。
- 贝叶斯优化/其他概率优化 —这种(贝叶斯优化)方法确实涉及数学,老实说,我还没有探索过其中的数学。我将给出它真正做什么的概述,即使你不知道它的内部工作原理,你仍然可以在程序中应用它,就像我们将在后面看到的。贝叶斯优化使用称为高斯过程的东西来猜测或建模目标函数(我们希望通过找到正确的超参数集来最小化的函数)。在这种方法中,对我们想要评估目标函数的次数设置了限制,因为它被认为是非常昂贵的。首先,在参数范围内确定一组随机点,以观察函数值。然后使用高斯过程来猜测目标函数。使用“采集函数”来决定下一个采样点。并且这个过程重复上面设置的“极限”次数。深入了解可以参考本和本。还有其他技术,如使用 Hyperopt 实现的 TPE (代码如下)。可以查看这篇论文了解 TPE。
网格搜索实现
如果你正在使用 SK-Learn 模型,那么你可以直接使用它们的 GridSearchCV 。使用起来相当简单。您可以访问上面的文档链接。一个优点是它也可以选择并行运行作业。如果你正在使用 Keras 模型,那么你将不得不使用 Keras 模型的包装器,如这里的所解释的。
但是如果它不起作用或者你不想学习新包的语法,你可以像这样实现一个简单的最小网格搜索:
其他更智能的搜索实现
有几个开源软件包可以使用其他“更智能”的搜索算法来最小化目标函数。我将展示远视和距骨的例子。下面是如何使用 Hyperopt 实现超参数调整,Hyperopt 使用 TPE 算法最小化函数。
Sample code for Hyperopt
在上面的代码片段中,变量“search_space”保存您想要搜索的参数及其值。最后的“fmin”函数是进行最小化的实际函数。这个程序本身非常简单,我已经给它添加了注释,所以理解起来应该不成问题。但是我想更深入地挖掘搜索空间字典的形成,因为这有点尴尬。让我们把它放在显微镜下:
主字典保存了我们想要优化的参数的所有键。我们将主要处理两个函数(或者他们称之为随机表达式),即选择和一致。" hp.choice “接受一个值列表,从中进行尝试。这个函数然后返回其中一个选项,应该是一个列表或元组。我们传递给 hp.choice 和 hp.uniform 的字符串(第一个参数)主要供 Hyperopt 内部使用。” hp.uniform "统一返回第二个和第三个参数之间的值(低和高)。棘手的地方在于,当你有“lstm_layers”这样的参数时,它定义了网络中 lstm 层的数量。对于这样的参数,我们有两组新的参数要测试:第一组是当网络中的 LSTM 层数为 1 时,第二组是当使用两个 LSTMs 层时。在这种情况下,您可以看到我使用 hp.choice 告诉系统我对该参数(lstm_layers)的选择,该参数的值由键“layers”给出。当 Hyperopt 使用两个 lstm 层测试模型时,它将考虑另外两个参数进行测试,即第二个 LSTM 层中的节点数(lstm2_nodes)和用于第二个 LSTM 层的漏失(lst m2 _ 漏失)。我保留了第一个 lstm 层空白,但你也可以包括其他参数来测试。
我希望我清楚如何为实际目的构建样本空间。现在让我们看看另一个声称使用概率方法(结合网格或随机)来减少评估数量的库— Talos 。
Code snippet for Talos
Talos 的用法类似于以前的工具;你必须创建一个函数来建立模型,训练它,根据验证数据对它进行评估。唯一的区别是模型函数返回 Keras 历史对象和模型,而不是字典。这里要注意的另一件事是,我们在 Scan 函数中传递 X 和 Y,但它们从未被使用过,因为我们想要在当前迭代中基于所选的“batch_size”和“time_steps”构建数据。但是对于更简单的问题,它会更容易。
你也可以使用Hyperas(hyperpt+Keras),它是 hyperpt 的一个包装器。主要的优点是你不需要学习任何新的语法/功能。你所要做的就是像前面一样定义一个搜索空间字典,然后像下面这样建立你的模型。您所要做的就是将您想要测试的参数值放在双花括号中(例如{{ [1,2,3] }})。
但是 Hyperas 在这种情况下不起作用,因为我是从’ model ‘函数调用’ data '函数,使用双花括号的语法导致了一些问题。我没有深究这个问题,因为我已经有了其他工具。尽管如此,我认为这里值得一提,因为这要容易得多。
这很好,但是如何知道这些工具返回的结果是最好的呢?如果你问我,我们不会知道。您必须权衡利弊——您必须在微调上花费多少时间,以及有多少验证损失对您来说是足够好的。在我看来,如果你试着花一些时间对上述工具的结果进行手动调整,那将是最好的。
例如,当我处理股票数据集时,我首先编写了自己的网格搜索实现,并在云上运行。然后,我尝试了上面的这些工具进行智能调优,但不幸的是,云虚拟机非常慢(比我的笔记本电脑还慢!)那天,我已经没有耐心了。幸运的是,当我读到这些工具,实现并开始在云上运行它们时,我的网格搜索已经完成了。我对这些值进行了网格搜索:
search_params = {
"batch_size": [20, 30, 40],
"time_steps": [30, 60, 90],
"lr": [0.01, 0.001, 0.0001],
"epochs": [30, 50, 70]
}
这个只有 4 个参数(81 个组合)的搜索运行了 24 个小时!由于兴奋中忘记实现登录(错误 1 ),所以没有结果在此分享。通过使用网格搜索的结果,我得到了令人失望的预测:
Initial Result
但是我还没有优化其他的东西,比如层数等等(我从一个有 2 个 LSTM 层和 1 个密集层的神经网络开始— **错误 2。**总是从更简单的模式开始,先试水,然后逐步发展。反正我决定进一步手动优化。我采用了网格搜索的最佳结果,并尝试了其他参数。无论我如何努力,我都不能改善损失。我知道这是过度拟合,所以我试图增加辍学,但无济于事。然后我顿悟了,可能是我太努力了,1 个 LSTM 层就够了(是的,我知道我很蠢— 错误 3 低估了神经网络的力量)。但是你怎么知道模型是否过度拟合呢?其实挺简单的。在我的例子中,训练错误与验证错误看起来像这样:
首先,当你看到训练数据和验证数据之间的巨大差距时,这是过度拟合。这背后的逻辑是,您的模型从您的训练数据中学习得很好,但它无法将其推广到验证数据(新数据)。第二件事情是验证错误情节的诡异形状。到处都是。这意味着模型只是预测新数据的随机值,这就是为什么跨时代的验证损失之间几乎没有关系。另一件要找的事情是纪元日志。如果你看到训练损失持续减少,但验证损失波动或一段时间后保持不变,这可能是过度拟合。
因此,我删除了 LSTM 的第二层,并添加了下降层,其值高于我一直使用的值(0.2 到 0.5)。瞧啊。
final plot
final train vs validation loss. Notice how the gap is gradually reducing
巨大的进步对吗?我很确定,如果我们付出更多的努力,我们会做得更好。
我没有分享训练模型的代码片段,因为它与上面的代码片段相同;只有正确的参数。你可以在我的 Github 简介这里找到所有完整的程序。
在的下一篇文章中,我将分享一些重要的工具/技巧,它们在这个项目中对我帮助很大,但通常没有给予足够的关注。
寻找正确的模型参数
Photo by Jason Wong on Unsplash
如果您一直在阅读有关数据科学和/或机器学习的书籍,那么您一定会接触到与 MNIST 数据集相关的文章和项目。该数据集包括一组 70,000 张图像,其中每张图像都是从 0 到 9 的手写数字。我还决定使用相同的数据集来了解微调机器学习模型参数如何产生差异。
这篇文章解释了我如何使用GridSearchCV
来寻找这个数据集的最佳拟合参数,并使用它们来提高精确度和改善混淆矩阵。您可以在下面的 GitHub 资源库中找到代码:
该项目包括使用 GridSearchCV 来确定估计器参数的最佳组合。…
github.com](https://github.com/kb22/Digit-Recognition-with-Parameter-Tuning)
导入库和数据集
我首先导入必要的库。我用训练和测试数据作为.csv
从到这里。数据集中的每一行都由一个标签和 784 个像素值组成,以表示 28x28 的图像。
训练数据包括 60,000 幅图像,而测试数据集包括 10,000 幅图像。
一旦我有了数据,我就从中获取特征和标签,并将其存储在train_X
、train_y
、test_X
和test_y
中。
探索数据集
分析阶级分布
正如我在以前的文章中所讨论的,每个类的数据应该大致相同,以确保正确的模型训练没有偏见。
Count of images for each digit (0–9)
如果我们看这个图,每个数字的计数都有一些差异。然而,差别不是太大,模型仍然能够很好地根据数据进行训练。因此,我们可以更进一步。
查看训练图像
让我们看看真实的图像。我从训练数据中随机选择 10 张图像,并用plt.imshow()
显示出来。
10 Randomly selected images from the dataset
在这 10 张随机图像中,我们立即看到的是任何一种类型的数字之间的差异。看看上面 10 张图中的所有4
。第一个是粗直的,第二个是粗斜的,第三个是细斜的。如果模型可以从数据中学习,并实际检测出4
的所有不同风格,那将是非常令人惊讶的。
应用机器学习
我决定使用随机森林分类器对训练数据进行训练,并对测试数据进行预测。我使用了所有参数的默认值。
接下来,利用预测,我计算了准确度和混淆矩阵。
该模型达到了 94.4%的准确率。混淆矩阵表明,该模型能够正确预测大量图像。接下来,我决定调整模型参数,尝试改善结果。
参数调谐
为了确定模型参数值的最佳组合,我使用了GridSearchCV
。这是由sklearn
库提供的一种方法,它允许我们定义一组我们希望为给定模型尝试的可能值,它根据数据进行训练,并从参数值的组合中识别最佳估计值。
在这个特例中,我决定为几个参数选择一个值范围。估计器的数量可以是 100 或 200,最大深度可以是 10、50 或 100,最小样本分裂为 2 或 4,最大特征可以基于sqrt
或log2
。
GridSearchCV
期望估计量,在我们的例子中是random_forest_classifier
。我们将可能的参数值作为param_grid
传递,并将交叉验证设置为 5。将verbose
设置为 5 会向控制台输出一个日志,而njobs
设置为-1 会使模型使用机器上的所有内核。然后,我拟合这个网格,用它来寻找最佳估计量。
最后,我使用这个最佳模型来预测测试数据。
看看上面的精度,我们看到,仅仅通过改变模型的参数,精度就从 94.42% 提高到了 97.08% 。混淆矩阵还显示更多的图像被正确分类。
机器学习不仅仅是读取数据和应用多种算法,直到我们得到一个好的模型,但它也包括微调模型,使它们最适合手头的数据。
确定正确的参数是决定使用哪种算法并根据数据充分利用算法的关键步骤之一。
结论
在本文中,我讨论了一个项目,其中我通过使用GridSearchCV
选择参数值的最佳组合来提高随机森林分类器的准确性。我使用 MNIST 数据集,将准确率从 94.42%提高到 97.08%。
阅读更多文章:
基于搜索查询推荐文章
towardsdatascience.com](/lets-build-an-article-recommender-using-lda-f22d71b7143e) [## 使用 Flask、Flask RESTPlus 和 Swagger UI 处理 API
Flask 和 Flask-RESTPlus 简介
towardsdatascience.com](/working-with-apis-using-flask-flask-restplus-and-swagger-ui-7cf447deda7f) [## 使用机器学习预测心脏病的存在
机器学习在医疗保健中的应用
towardsdatascience.com](/predicting-presence-of-heart-diseases-using-machine-learning-36f00f3edb2c) [## matplotlib——让数据可视化变得有趣
使用 Matplotlib 创建世界各地人口密度的美丽可视化。
towardsdatascience.com](/matplotlib-making-data-visualization-interesting-8bac1eb3d25c)
请随意分享你的想法和想法。也可以通过 LinkedIn 联系我。
用 BigQuery 寻找顶级编程语言
如果你一直关注谷歌的云平台,你对 BigQuery 并不陌生。在我看来,BigQuery 是谷歌武库中最具差异化的工具。凭借 Pb 级的仓储能力、数百个开源数据集和熟悉的 SQL 接口,开发人员的准入门槛非常低。
上周,我在浏览 BigQuery 中的公开数据集时,碰巧发现了 Github 的数据集。它有提交数据、编程语言数据、存储库内容数据和许多其他酷表。
在浏览了整个数据集之后,我可以想到很多分析查询,我可以在数据集上运行这些查询来获得一些天才的见解。例如,我可以使用存储库内容数据集来找出最流行的 Java 包,使用提交数据集我可以找出哪个作者提交了最多的提交,或者我可以使用语言数据集来根据存储库计数找出顶级编程语言(这恰好是本文的主题)。
在回答这个有名无实的问题时,我们将学习 BigQuery 中的三个概念。
- UNNEST()和重复数据类型
- 窗口和 Rank()函数
- 命名子查询
设置
首先让我们将数据集添加到我们的 BigQuery 控制台。在 GCP web 控制台中导航到 BigQuery。
在左侧面板上点击添加数据>锁定项目。
输入项目名称 bigquery-public-data 。点击 pin ,你应该会看到项目被固定在窗格中。
您也可以转到数据集页面这里并点击查看数据集,这也应该会将项目添加到您的 BigQuery 仪表板。
该项目包含 BigQuery 托管的所有公共数据集,您需要导航到 github_repos 数据集。在里面你会看到 9 张桌子。
Public Github data set in BigQUery
出于本帖的目的,我们将使用 语言 表,该表具有以下模式。
Schema for languages table
让我们通过查询记录来了解数据。
SELECT * FROM `bigquery-public-data.github_repos.languages` LIMIT 1000
您会注意到表中的 language 字段是记录类型(也称为 STRUCT)的嵌套重复列,这是正确的,因为一个存储库可以有用多种语言编写的代码。你可以在这里读到更多关于他们的信息。
这使我们想到了使用重复(数组)类型的第一个概念。
UNNEST()和重复数据类型
为了使用 language 字段,我们首先需要展平该列,这样我们可以在单独的行中获得每个值,而不是在一行中获得该字段的所有值。我们将使用 UNNEST()函数,正如它的名字一样,它将嵌套的记录解嵌套到单独的行中。
SELECT
repo_name,
arr.name AS LANGUAGE,
arr.bytes AS language_bytes
FROM
`bigquery-public-data.github_repos.languages`,
UNNEST(LANGUAGE) arr
LIMIT
10
每种语言记录都有语言名称和语言字节,其中存储了库中有多少字节的代码是用任何特定的语言编写的。在这篇文章中,我们将用代码中最大字节数的语言标记一个存储库,这将我们带到窗口和排名的下一个概念。
窗口和 Rank()函数
对于每个存储库,我们想找出哪种语言拥有最大的字节数,我们将通过对每个存储库中的语言进行排序来实现这一点。我们将使用前面的查询作为子查询来构建我们的查询。
SELECT
repo_name,
LANGUAGE,
RANK() OVER (PARTITION BY t1.repo_name ORDER BY t1.language_bytes DESC) AS rank
FROM (
SELECT
repo_name,
arr.name AS LANGUAGE,
arr.bytes AS language_bytes
FROM
`bigquery-public-data.github_repos.languages`,
UNNEST(LANGUAGE) arr ) AS t1
LIMIT
10
这将向我们的结果添加一个等级列,我们需要选择等级=1 的行,因为这些行代表每个存储库的顶级语言。
SELECT
t2.repo_name,
t2.LANGUAGE
FROM (
SELECT
repo_name,
LANGUAGE,
RANK() OVER (PARTITION BY t1.repo_name ORDER BY t1.language_bytes DESC) AS rank
FROM (
SELECT
repo_name,
arr.name AS LANGUAGE,
arr.bytes AS language_bytes
FROM
`bigquery-public-data.github_repos.languages`,
UNNEST(LANGUAGE) arr ) AS t1 ) AS t2
WHERE
rank = 1
LIMIT
10
现在我们的数据集已经准备好了,可以根据存储库的数量找到最常用的语言。我们将创建一个命名查询,它可以在后续查询和子查询中使用,以获得更好的可读性。
命名子查询
BigQuery 支持 WITH 关键字来创建命名查询,这有助于您从中进行选择。命名查询没有具体化,每次引用时都会计算整个查询。你可以在官方文档这里阅读更多关于 WITH keyword 的内容。
WITH
repositories AS (
SELECT
t2.repo_name,
t2.LANGUAGE
FROM (
SELECT
repo_name,
LANGUAGE,
RANK() OVER (PARTITION BY t1.repo_name ORDER BY t1.language_bytes DESC) AS rank
FROM (
SELECT
repo_name,
arr.name AS LANGUAGE,
arr.bytes AS language_bytes
FROM
`bigquery-public-data.github_repos.languages`,
UNNEST(LANGUAGE) arr ) AS t1 ) AS t2
WHERE
rank = 1)
如果您复制粘贴这个查询,您将得到一个语法错误,,因为如果不在任何后续的 SELECT 语句或表达式中使用它,您就不能拥有一个命名的子查询。
该查询将返回两列存储库名称(repo_name)及其语言。
从这里开始,通过计数找到顶级存储库应该是小菜一碟。
该查询应该产生以下结果集。
Top 10 programming languages by repository counts on Github
这个查询的结果并不令人震惊,随着 web 应用程序和 SPAs 的出现,JavaScript 在图表中名列前茅。
现在你知道了。按存储库数量排名的前 10 种编程语言。
如果你在代码中发现任何错误或者有任何问题,请在下面留下你的评论。
直到那时快乐编码。
用人工智能找到你需要的东西
路径搜索算法介绍
大三的时候上了第一堂人工智能课。我神采奕奕地走进去。我期待着所有的热门词汇——神经网络、支持向量机、贝叶斯网络等等。第一堂课是我杜克经历中最令人失望的 75 分钟。我得到了深度优先搜索和递归,而不是听起来很酷的时髦词汇。
这是我最喜欢的课程之一,我的教授非常清楚他在做什么。我们必须先学习基础知识,然后才能做大事。如果我们不了解内部和外部的基本原理,我们就没有对抗深度学习的机会。宫城先生在教我们给汽车打蜡。路径搜索打开,路径搜索关闭…
Copyright 1984 Columbia Pictures — The Karate Kid
**尽管如此,随着我了解的越来越多,我记得我对路径搜索算法嗤之以鼻。他们看起来一点也不聪明。**你是说你只是生成所有可能的路径,猜测其中最好的一条,然后探索那条路径?人类比这聪明多了。我们处于食物链的顶端是有原因的!
但那次演讲后我不得不去办点事。我得去吃午饭,买课本,然后去上另一节课。所以,我想“我可以吃午饭,然后去上课,然后拿我的课本。啊,但是等等,那时候商店已经关门了。也许我会去拿书,然后吃午饭,然后去上课。但是,不,我现在饿了。好吧,我去吃午饭,拿书,然后去上课。“如果你错过了这里的讽刺,再读一遍。
要么是计算机非常聪明,要么是人类没有我们想象的那么聪明。我倾向于后者。
概观
在本文中,我们将介绍四种搜索算法来解决下面的问题。假设给你一个二维网格。你从左上角开始,只能向下或向右移动。网格中的每个单元格都有一个点值。收集路径上所有像元的点值。你的目标是收集尽可能多的分数。
递归
我们首先尝试一种递归方法。递归简单易行。我们的基本情况是到达右下角的单元格。否则,我们将递归调用向下向右移动。然后我们在每一步取这两个调用的最大值。这允许我们将问题分解成更小的子问题。这种方法的实现如下。
Code source here.
深度优先搜索
这种方法可行,但是递归调用代价很高。随着网格的增长,我们可能会遇到堆栈溢出。当我们进行的递归调用超过内存中的堆栈所能处理的数量时,就会发生这种情况。因此,我们转向一种深度优先搜索(DFS)方法。
在 DFS 中,我们会先充分探索一条道路,然后再转向另一条。我们使用堆栈来跟踪我们的路径。我们尽可能向下移动,然后向右移动。我们仍然在探索与递归解决方案一样多的路径,但是我们使用了更少的内存并防止了堆栈溢出。对于长度为 2 的二维网格,我们的堆栈如下所示。
我们必须做一些跑腿的工作来编写我们的 DFS 算法。我们将定义一个 GridCell 类。对于每个单元格,我们将记录我们是从这个单元格向下还是向右。对于每个完整路径,我们将记录分数。如果它大于我们当前路径的分数,我们将把我们的最大值和最优路径更新为当前值和路径。这显示在下面的代码中。
Code source here.
双向搜索
我们仍然没有解决算法中的一个主要问题。我们重复计算来自同一个单元的所有路径。这导致了许多重叠的子问题。例如,考虑下面大小为 n = 5 的网格。大致有 nn种方法可以到达中间的细胞。从中间的单元格,有大约 n 多种方式到达右下角的单元格。这意味着我们正在为此路径和所有其他路径进行 n 计算。这是非常低效的。
如果我们能从角落开始呢?如果我们从左上和右下开始工作,然后在中间相遇会怎么样?然后,我们可以从上半部分和下半部分分别选择最佳路径,并将它们配对。我们现在只做 n 计算,而不是 n 计算。这是一个巨大的进步,尤其是对于大 n 来说。这被称为双向搜索,如下图所示。注意箭头的方向。
但是,我们可以做得更多。我们可以利用现代计算机的处理能力。当你的 Python 代码运行时,它被分解成你的 CPU 可以执行的代码。开箱即用,Python 代码不支持并行性。出于我们的目的,我们认为并行是功能的同时执行。现在,我们的代码线性运行。如果 funcA() 在 funcB() 之前, funcA() 需要在 funcB() 开始之前终止。
然而,事实并非如此。如果 funcA() 和 funcB() 是独立的,我们可以同时运行。我们可以使用 Python 中的多处理来利用并行性。这意味着我们可以同时计算左上和右下的路径。
我们并行执行双向搜索,并在运行时看到了相当大的改进。这个实现的代码可以在这里找到。但是,对于非常大的 n ,这个问题还是需要一段时间。为了解决这个问题,我们求助于启发式搜索。
启发式搜索
我们的下一个算法利用了搜索和人工智能中的一个关键概念。 启发式 的想法。启发式是一种评估功能,帮助我们估计达到目标的最佳路径。在每次迭代中,我们将启发式算法应用于所有可能的未来状态。然后我们探索哪个状态具有最高启发值的路径。
我们需要为我们的问题定义一个试探法。假设我们位于网格的左上角。无论我们向右还是向下移动,所有灰色的单元格都是可以访问的。然而,如果我们向下移动,就会失去橙色细胞,如果我们向右移动,就会失去蓝色细胞。因此,我们可以查看橙色单元格和蓝色单元格的平均单元格值。我们将朝着更高的平均单元格值的方向移动。我们可以对每个细胞都这样做。
这个解决方案不精确。但是,它快得令人难以置信。我们也看到相对最优的解决方案。如果我们的单元格值范围从 1 到 5,我们会看到对于网格大小为 n = 13 的解决方案有 90%是最优的。随着单元格范围的增加,我们看到的最优解会越来越少。例如,在 1 到 100 的范围内,我们看到对于 n = 13 有 85%的最优解。这可能不会给你一条去月球的路,但会给你一条去杂货店的路。
这种启发式搜索算法的实现如下。如果你感兴趣,可以随意克隆库并尝试启发式方法。更精确的启发式方法会导致更优的解决方案。
Code source here.
结束语
这个问题在现实世界中有很多应用。一个明显的例子是 GPS 应用程序规划路线。我们可以根据道路的速度限制除以距离来给道路赋值。在这里找到一个解决方案可以让我们找到一条快捷的路线。但是如果我们能给代表一个问题的状态分配点值,我们可以使用这些算法中的任何一个。我们可以很容易地使用这些完全相同的算法来为新 iPhone 的发布找到最佳的营销策略。
这也凸显了很多搜索问题的问题。随着这些问题越来越多,找到最佳解决方案需要太长时间。因此,我们必须在速度和优化之间做出权衡,正如我们在这里所做的那样。
感谢阅读!
疑问?评论?在【andrew.oliver.medium@gmail.com】给我发邮件。我很想收到你的来信!
数小时内找到梦想中的家——数据驱动的方式
使用数据和经过测试的流程寻找梦想家园,减少时间和困惑。不需要编码。
时间太少,选择太多,而且没有明显的好选择
找到你梦想中的家是生活中更令人兴奋的追求之一。但这也是一项极其艰巨的任务。我们许多人花费大量的时间、思想、精力,当然还有金钱,去寻找一个我们称之为家的地方。我知道这一点,因为这是我在街上和地铁里听到的所有人谈论的话题。这是一个全面的项目,几乎不可能与生活、家庭和工作的其余部分一起管理。许多人开玩笑说,他们最好辞掉工作,以便最终能够给找房子以适当的关注。他们说,太糟糕了,如果他们这样做了,也无法获得抵押贷款。
真的,谁能责怪他们呢?使用一些基本参数,在您选择的酒店门户上启动搜索。如果你住在一个城市,你可能会看到大量的结果,每个结果的缺点和好处,你必须以侦探的方式去发现。一个。由。一个。
几个月前,当我开始梦想在伦敦有一个新家时,我就在那里(尽管,就本文而言,它也可能在纽约、新加坡或旧金山)。幸运的是,我在一家分析公司工作,因此有一种即时的冲动,认为一定有更好的方法——即使你自己不是数据科学家。确实有。对我来说,答案在于数据驱动的方法。
接下来仍然是大量的工作——不要误会我的意思。像以往一样,找到正确的数据,然后找到处理它的方法仍然很棘手,所以我在下面总结了我的过程,希望对你有所帮助。您的位置和要求会有所不同,但我相信这个过程会与您的搜索和个人限制相类似。
位置,位置,还有…什么?
列出你的优先事项
在选择房产时,有许多方面很重要。找房子总是要妥协的。有些事情你可能不太愿意向前看。重要的是你设定自己的极限,并坚持下去。一旦你开始改变你的想法太多,你基本上回到考虑所有的选择。然而,这是一个毫不妥协的排斥游戏,只有当你清楚什么对你来说是重要的,你才能成功地做到这一点。我还将这个列表保留为主要的环境因素,因为我们将在缩小地理区域后看看你喜欢什么类型的房产。
我的一些优先事项是:
- 我的预算+/- 20%
- 最少一间卧室(无工作室)
- 步行 15 分钟内有一大片绿地
- 不到 45 分钟的通勤时间(对我和我妻子来说)
- 入室盗窃和暴力犯罪率低
- 高空气质量
获取数据,并绘制地图
既然我们有了优先事项,我们需要做一些研究。正如我在数据咨询公司 QuantumBlack 的工作一样,找到并探索正确的数据是最关键的一步。我是一个视觉型的人,所以我总是寻找已经以地图的形式出现的数据,或者可以使用免费的在线工具放入其中的数据。
预算
One of the many considerations FindProperly can help you with.
对于我的首要任务来说,一个惊人的资源是find proper。该网站提供了一系列惊人的工具来帮助你找到你实际上能负担得起的住处,等等。
您的“位置”
Which places do you visit often?
为了找出你实际上应该住在哪里,制作一张你最重要地点的地图。通过创建一个你的位置历史热图,它也可以帮助你标出你过去去过的很多地方。
思考一下:
- 工作场所
- 学校
- 最好的朋友
- 机场
- 公园
- 主要交通枢纽
- 运动设施
- 等等。
一旦你规划好了关键地点,你就可以使用 Mapumental 这样的工具来设定出行时间的限制。
噪音
I like my peace and quiet.
噪音水平会对你新家的生活质量产生重大影响。你可能想留意汽车交通、铁路线和飞行路线。伦敦的前两个可以在这里找到。
空气污染
This air pollution map really made me want to live in N6
空气污染地图可能很难找到,但是对于伦敦来说,这张应该可以帮你开始。就污染物而言,你可能要注意这四种:
- 二氧化氮
- 臭氧
- PM10
- PM2.5
犯罪
Explore the Metropolitan Police’s crime dashboard
不是所有的罪行都是平等的。区分哪种犯罪对你作为居民的影响最大是值得的。就我个人而言,我优先考虑盗窃和暴力犯罪。前往遇见警察浏览他们的地图。
为你的房产寻找优先区域
组合您的数据
现在你已经有了对你来说很重要的所有东西的地图,你需要把它们结合起来找到你要寻找的区域。简单的方法是并排看地图,或者抓图,然后用 photoshop 等图片编辑工具叠加。
如果你想更进一步,你可以从上面的网站下载数据(大多数网站都有下载选项),并将其加载到地图网站,如 kepler.gl 。
Crime data from the Metropolitan Police loaded into Kepler.gl
探索入围区域
厌倦了坐在电脑前?很好,因为下一步你必须站起来出去。理想情况下,你会把搜索范围缩小到两三个区。现在,你需要去拜访他们。去逛逛,感受一下这个地方,看看是不是“你”。你能想象自己在街角的咖啡馆里度过一个上午吗?你喜欢那边的古董市场吗?这些学校看起来像什么?离开大街。你能想象自己住在什么安静的树叶茂密的街道上吗?
心选择一个地方就像选择一个家一样。访问你的入围地区应该有助于你进一步缩小搜索范围。
找到你的新家
决定代理机构
现在,也只有现在,打开一个房产搜索网站。定义您在地图要素上选择的区域,并浏览结果列表。这还是研究。你实际上还没在找唯一的房产。即使你正在寻找一个家,在许多市场,当一个地方出现在比如说 Zoopla 上时,你已经太晚了。这是因为房产中介公司会给邮件列表上的客户第一次浏览的机会,所以一旦你在网上看到一个广告,成百上千的其他人可能在一周前就已经收到了。
相反,你要找的是哪些代理机构代表了吸引你的房产类型。在网上列出你喜欢的地方,然后检查它们是由哪个代理机构代理的。你可能会发现有几个是由同一家公司代理的。当我完成这一步时,我喜欢每个区域的 10 个地方。四个坐在代理处 A,三个坐在代理处 B,三个坐在代理处 c。挑选合适的房产数量最多的代理处,走进去,和他们握手,然后进入他们的电子邮件列表。
等待
从现在开始,这是一个等待的游戏。我的最后一条建议是,如果可以的话,耐心点。如果你遵循了所有这些步骤,你很快就会在你的收件箱里找到你梦想中的家,并且可以安排看房——而不用几个月来每天晚上都去搜索房产网站。
回家
我们的结果?整个任务总共花了我几个小时(我希望你现在能少花点时间)。我们最后看了四处房产。我们选择了我们看到的第三个。当然,这是谈判和合同真正疯狂的时候,但那是另一回事了。为此,我只能祝你好运。
如果你用数据找到了梦想中的家,请在评论中分享你的经历。我很想听听。
最初发表于【https://www.thomasessl.com】
句子嵌入的细粒度分析
将单词或句子表示为高维空间中的实值向量,使我们能够将深度学习方法融入自然语言处理任务中。这些嵌入作为从序列标签到信息检索的各种机器学习任务的特征。单词 2Vec(及其变体)是通过使用分布假设*(出现在相同语境中的单词往往具有相似的含义)*来生成单词嵌入的 go 模型。
然而,当一个人想要对可变长度的句子进行编码时,事情就变得更加棘手了。在自然语言处理领域,为可变长度的句子生成固定长度的嵌入已经有了很多的研究。这些固定长度的句子嵌入对于涉及句子级语义的任务(如文本摘要)至关重要。
Similar sentences have similar embeddings. Image from TechViz
生成句子嵌入的最简单方法是简单地连接组成单词的单词嵌入。然而,这为不同长度的句子产生了可变长度的嵌入。对于期望固定长度的特征向量的下游模型,这是有问题的。因此,我们需要想出一种方法,将单词嵌入简化为一个固定长度的向量,该向量可以捕捉句子的所有重要方面。
有两种流行的生成固定长度句子嵌入的技术:
- 连续单词袋(CBOW)
- 序列对序列模型,如 rnn 或 LSTMs
连续的单词袋
CBOW 方法不考虑句子中单词的顺序。这是一种非常天真的方法,只需对所有组成单词的单词嵌入进行求和(或求平均值)来生成句子嵌入。
CBOW 是一个重载术语,也是用于训练 word2vec 算法的一种算法的名称。基本上,单词包用于描述任何忽略单词顺序的算法或嵌入技术。连续来自于这样一个事实,即我们在实值向量的域中操作。因此,CBOW 是一种建模方法,在这种方法中,人们在组合单词嵌入时忽略了单词排序。
人们可能会觉得这是一种非常糟糕的获取句子信息的方式,因为它完全忽略了词序。
例如:“努力学习,不要玩!”、*“玩命,不学习!”*将具有相同的【CBOW 嵌入,尽管它们的含义与相反。
然而,CBOW 在实践中被证明是一种有效的嵌入技术,并且在更复杂的模型出现之前是标准的。
序列对序列模型
像 RNN、LSTM 这样的序列到序列模型逐个处理单词嵌入,同时保持存储上下文信息的隐藏状态。在处理一个句子结束时的隐藏状态实质上编码了来自整个句子的信息,因此代表了该句子的嵌入。
Input is the word embedding for (t+1)th word, and the (t+1)th output is the context aware embedding for that word. The state at this stage represents the context embedding till the (t+1)th word.
该架构以编码器-解码器的方式训练,其中 RNN 或 LSTM 充当编码器。由编码器产生的嵌入被馈送到执行一些其他任务的解码器。在这个任务上的损失训练了编码器-解码器架构,此后可以丢弃解码器,而仅仅使用编码器作为嵌入生成器。
这种产生句子嵌入的方法能够基于句子中的单词排序进行区分,因此可能提供比 CBOW 模型更丰富的嵌入。
分析嵌入
由上述两种方法生成的句子嵌入对于解释来说是高度不透明的,并且不能直接评估它们的强度。评估这些嵌入的唯一可能的方法是使用利用这些嵌入的下游任务,然后比较这个复合模型在任务上的性能。很明显,这不是最理想的,而且还有很多需要改进的地方。要打开这个黑箱, 使用辅助预测任务对句子嵌入进行细粒度分析(阿迪等人) 步骤在。他们提出了一种方法,在细粒度级别上比较句子嵌入的基本句子特征,如句子长度、句子中的项目及其顺序。
方法学
对于句子的每个低级特征,制定一个预测任务,并为该任务训练一个分类器网络。分类器的性能显示了句子嵌入能够多好地捕捉该特征。由于这些特征是可直接解释的句子的低级属性,因此该实验提供了对句子嵌入质量的洞察。
正在考虑的特征是:
- **句子长度:**给定一个句子嵌入,分类器网络必须预测句子的长度(句子被分入不同的类别)。
- **单词出现:**给定一个句子嵌入 s 和一个单词嵌入 w ,分类器要预测这个单词是否出现在句子中。
- **单词排序:**给定一个句子嵌入 s 和单词嵌入 w1 和 **w2,**分类器需要预测哪个单词先出现。
考虑中的嵌入是使用 word2vec 嵌入上的平均的 CBOW,以及 LSTM 自动编码器-解码器。自动编码器-解码器意味着网络被训练产生与输出相同的输入。解码器和编码器都是 LSTM,编码器 LSTM 用于生成句子嵌入。
注:图例中的“Perm”指的是句子中的某些单词随机排列的实验。
结果
- 长度实验
From the paper
显然,LSTM 架构能够对句子长度进行编码,准确率超过 85%。然而,更令人惊讶的是,与 20%的大多数预测准确性相比,CBOW 表现得非常好(65%)。
当人们看到句子嵌入的规范与句子长度的关系图时,可以解释 CBOW 的这种令人惊讶的表现。
随着越来越多的单词嵌入被平均在一起,总和接近于零。单词嵌入在原点周围相当均匀地分布,因此根据中心极限定理,可以预期随着越来越多的嵌入被添加,总和接近零。因此,嵌入的规范作为句子长度的指标。
- 词出现
CBOW 的真正实力在这里可见一斑。对于低维嵌入,CBOW 能够比更复杂的顺序模型更好地捕捉单词身份。令人惊讶的是,性能随着维数的增加而降低。
- 词序
正如预期的那样,LSTM 嵌入能够很好地捕捉单词的排序,并且随着嵌入维度的增加,性能也增加。然而,当考虑到 CBOW 模型没有试图保留任何单词顺序时,CBOW 的性能也是值得注意的,正如我们在上面看到的那样。CBOW 在平衡类设置中给出 70%的准确度,即随机预测准确度为 50%。
作者假设大部分词序信息是在词序统计中获得的,即统计上某些词出现在其他词之前。这一假设得到了一项实验的进一步支持,在该实验中,作者完全放弃了句子嵌入,而只使用单词嵌入来预测单词排序。
编码器-解码器架构的性能也下降到 CBOW 的水平。这表明一些关于词序的信息是由词序统计本身捕获的,而额外的排序信息是由句子排序提供的。
即使去除句子嵌入,CBOW 模型的性能也几乎不受影响。
最后的想法
我们看到,尽管 CBOW 是一个如此简单的模型,但它在某些任务上却惊人地有效。对于低维嵌入,它能够很好地保持单词的同一性,并且在一定程度上能够对句子长度和单词排序进行编码。
LSTM 嵌入在编码低级句子属性方面非常有效。然而,增加嵌入的维度超过某个点提供了边际收益,事实上在某些情况下是有害的。
最后,实验只考虑了低级的句子属性。这显示了嵌入在捕捉句子的表面方面的有效性,但是没有显示语义概括或更深的句法方面的许多细节。对于这种概括,需要考虑单独的辅助任务,作者将其作为未来的工作。
免责声明:本文提供了一个构建来介绍在 中提到的使用辅助预测任务对句子嵌入进行细粒度分析的结果(Adi 等人;17) *。*所有显示结果的图片都来自论文,许多讨论和假设都直接取自论文,只是稍加转述。
干杯!
Python 中的细粒度情感分析(第 1 部分)
在这篇文章中,我们将评估和比较 5 类斯坦福情感树库(SST-5)数据集的几个文本分类结果。
Source: Pixabay
“学会选择很难。学会做好选择更难。在一个充满无限可能的世界里,学会做出正确的选择更难,或许太难了。”—巴里·施瓦茨
当开始一个新的 NLP 情感分析项目时,为一个给定的应用缩小选择方法的范围可能是一个相当艰巨的任务。我们是使用基于规则的模型,还是根据自己的数据训练模型?我们应该训练一个神经网络,还是一个简单的线性模型就能满足我们的要求?我们是否应该花时间和精力来实现我们自己的文本分类框架,或者我们是否可以使用一个现成的框架?解释结果和理解为什么做出某些预测有多难?
本系列旨在回答上述一些问题,重点是细粒度的情感分析。在余下的章节中,我们将使用 Python 中几个著名的 NLP 库来比较和讨论分类结果。下述方法分为三大类:
基于规则的方法:
- TextBlob :用于情感分析的简单的基于规则的 API
- VADER :基于简约规则的社交媒体文本情感分析模型。
基于特征的方法:
- 逻辑回归:sci kit-learn 中的广义线性模型。
- 支持向量机(SVM):sci kit 中的线性模型——用随机梯度下降(SGD)优化器学习梯度损失。
嵌入- 基础方法:
- FastText :一个 NLP 库,使用高效的基于 CPU 的单词嵌入表示来完成分类任务。
- Flair :一个基于 PyTorch 的框架,用于序列标记和分类等 NLP 任务。
在 Python 中,每种方法都是以一种面向对象的 方式实现的,以确保我们可以轻松地替换实验模型,并在未来用更好、更强大的分类器扩展框架。
为什么要细粒度的情感?
在今天的大多数情况下,情感分类器被用于二元分类(仅仅是正面或负面的情感),并且有充分的理由:细粒度的情感分类是一个 显著的 更具挑战性的 任务!细粒度情感的典型分解使用五个离散类,如下所示。正如人们可能想象的那样,由于人类语言的微妙之处,模型很容易在情绪强度的强弱方面出错。
Typical class labels (or intensities) for fine-grained sentiment classification
二元类别标签可能足以研究文本数据(如推文、产品评论或客户反馈)中的大规模积极/消极情绪趋势,但它们确实有其局限性。当使用比较表达式执行信息提取时,例如:“这款 *一加 X 型比三星 X 型好得多。”——*精细分析可以为优先解决客户投诉的自动化系统提供更精确的结果。此外,像“这样的双重性句子的位置实在令人厌恶…但是那里的人民是光荣的。“会混淆二元情感分类器,导致不正确的分类预测。
以上几点为解决这个问题提供了足够的动力!
斯坦福情感树库
斯坦福情感树库 (SST-5,或 SST-fine-grained)数据集是测试我们应用程序的合适基准,因为它旨在帮助评估模型理解句子结构的表达的能力,而不仅仅是孤立地查看单个单词。SST-5 由从带有细粒度情感标签的电影评论中提取的 11,855 个句子组成[1-5],以及组成数据集中每个句子的 215,154 个短语。
带有基于短语的细粒度情感标签的原始数据采用树结构的形式,旨在帮助从他们 2015 年的论文中训练一个 递归神经张量网络 (RNTN)。组成短语是通过使用斯坦福解析器解析每个句子(论文的第 3 节)并创建如下图所示的递归树结构来构建的。然后,在每个句子的树形结构上训练深度神经网络,以对每个短语的情感进行分类,从而获得整个句子的累积情感。
Example of Recursive Neural Tensor Network classifying fine-grained sentiment (Source: Original paper)
最先进的是什么?
最初在斯坦福论文【Socher et al .】中实现的 RNTN,在整句情感分类上获得了 45.7% 的准确率。最近,一个增强了 ELMo 嵌入的双注意分类网络(BCN)已经被用于在 SST-5 数据集上实现了 54.7% 的显著更高精度。SST-5 数据集上当前(截至 2019 年)最先进的准确度是 64.4% ,通过一种使用句子级嵌入的方法最初设计用于解决转述任务——它最终在细粒度情感分析上也表现得令人惊讶。
尽管神经语言模型自 2018 年以来变得越来越强大,但可能需要更大的深度学习模型(具有更多参数)和基于知识的方法(如图表)来实现足够的语义上下文,以在细粒度情感分析中达到 70-80%的准确率。
将数据集转换为表格形式
为了评估我们的 NLP 方法以及它们之间的区别,我们将只使用训练数据集中的 完整样本 (忽略组成短语,因为我们没有使用像斯坦福论文那样的递归的基于树的分类器)。使用 pytreebank 库将短语的树形结构转换为原始文本及其相关的类标签。这个树到表格转换的代码在这个项目的 GitHub repo 中提供。
Convert SST-5 tree data to tabular form that we can more easily work with
完整的句子文本及其类别标签(对于train
、dev
和test
集合)被写入单独的文本文件,在句子和类别标签之间使用制表符分隔符。
探索性数据分析
然后,我们可以使用 Pandas 更详细地研究表格数据集。首先,以数据帧的形式读入训练集,同时指定制表符分隔符以区分类标签和文本。注意“truth
”列中的类标签被转换为 Pandas 中的数据类型category
,而不是将其保留为字符串。
import pandas as pd# Read train data
df = pd.read_csv('../data/sst/sst_train.txt', sep='\t', header=None, names=['truth', 'text'])df['truth'] = df['truth'].str.replace('__label__', '')
df['truth'] = df['truth'].astype(int).astype('category')
df.head()
Sample of SST-5 training data
使用命令df.shape[0]
告诉我们我们有 8544 个训练样本。
数据集平衡吗?
在分析情感分类数据集之前,需要注意的一个重要方面是训练数据中的类别分布。
import matplotlib.pyplot as pltax = df[‘truth’].value_counts(sort=False).plot(kind=’barh’)
ax.set_xlabel(“Number of Samples in training Set”)
ax.set_ylabel(“Label”)
很明显,大多数训练样本属于类别 2 和类别 4(弱负/正类别)。相当数量的样本属于中性类。只有 12%的样本来自强负 1 类,这是我们评估分类器准确性时要记住的一点。
测试集呢?快速浏览一下,我们有 2,210 个测试样本,其分布与训练数据非常相似,同样,与其他类别相比,属于强阴性/阳性类别(1 或 5)的样本要少得多。这是可取的,因为我们的分类器进行预测的测试集分布与训练集的分布没有太大的不同。
原论文中提到的一个有趣的点是,很多真正的短文本例子都属于中性类(即 3 类)。这在熊猫身上很容易想象。我们可以创建一个新列来存储每个文本样本的字符串长度,然后按照文本长度的升序对 DataFrame 行进行排序。
df['len'] = df['text'].str.len() # Store string length of each sampledf = df.sort_values(['len'], ascending=True)
df.head(20)
Class labels for the really short examples in the test set
具有明显极性单词的样本,例如“good”和“loved”,将为情感分类器提供更大的上下文,然而,对于中性发音的单词(例如“Hopkins”或“Brimful”),分类器将不得不不仅与极小的上下文,即单个单词样本一起工作,而且还能够处理未出现在训练词汇中的模糊或看不见的单词。
数据标签并不完美!
正如论文中提到的,SST 数据集是由人类注释者通过 Amazon Mechanical Turk 标记的。注释者被展示随机选择的短语,他们从一个连续的滑动条中选择标签。基于多个标注者选择的标签的平均值,重构属于五个类别之一的离散情感标签。在标注过程中使用了随机抽样,以确保标注不受前面短语的影响。
Labelling interface for SST dataset (source: Original Paper)
上面的例子清楚地说明了为什么这是一个如此具有挑战性的数据集来进行情感预测。例如,注释者倾向于将短语“书呆子”归类为有点负面的,因为“书呆子”这个词在我们社会当前对书呆子的看法中有一些负面的含义。然而,从纯语言学的角度来看,这个样本也可以被归类为中性的。
因此,记住**文本分类标签总是受人类感知和偏见的影响是很重要的。**在现实世界的应用中,在主观的基础上看待某些边缘情况绝对有意义。没有一个基准数据集——推而广之,分类模型——是完美的。
记住这几点,我们可以继续设计我们的情感分类框架!
方法学
模型训练和评估的一般工作流程如下所示。
Sentiment classification: Training & Evaluation pipeline
**模型训练:**每个分类器(除了基于规则的分类器)使用监督学习算法在来自 SST-5 训练集的 8,544 个样本上进行训练。项目的 GitHub repo 中提供了单独的培训脚本。
**预测:**按照我们面向对象的设计理念,我们避免在不同的分类方法中重复执行相同任务的代码块。Python 中定义了一个Base
类,包含常用的方法:一个用于将 SST-5 数据读入 Pandas DataFrame ( read_data
),另一个用于计算模型的分类精度和 F1 值(accuracy
)。以这种方式将数据集存储在 Pandas DataFrame 中,可以非常方便地应用自定义转换和用户定义的函数,同时避免过度使用 for 循环。
Base utilities class for all classifiers
接下来,添加到我们框架中的每个单独的分类器必须继承上面定义的Base
类。为了使框架一致,每个新的情感分类器都包含了一个score
方法和一个predict
方法,如下所示。score
方法为文本样本输出一个唯一的情感类,而predict
方法将 score 方法应用于测试数据集中的每个样本,以在测试数据帧中输出一个新列'pred'
。然后通过使用在Base
类中定义的accuracy
方法来计算模型的准确性和 F1 分数是很简单的。
Example sentiment predictor class
**评估:**为了评估模型的准确性,使用 scikit-learn 和 matplotlib(GitHub 上的 plotter.py )绘制模型的 混淆矩阵 。混淆矩阵将每个类别的正确预测数与错误预测数进行列表,这样就可以更容易地看出对于给定的分类器,哪些类别的预测最不准确。请注意,我们的 5 类情况的混淆矩阵是一个标准化的反对角线矩阵——理想情况下,分类器的预测几乎 100%正确,因此反对角线之外的所有元素尽可能接近零。
Idealized confusion matrix (normalized) — termed an “anti-diagonal matrix”
培训和模型评估
在这一部分,我们将讨论每种方法的培训、情感评分和模型评估的一些要点。
1 —文本块
TextBlob 是一个用于处理文本数据的流行 Python 库。它构建在 NLTK 、之上,后者是 Python 的另一个流行的自然语言处理工具箱。TextBlob 使用情感词典(由预定义的单词组成)来为每个单词分配分数,然后使用加权平均值进行平均,以给出句子的总体情感分数。对每个单词计算“极性”、“主观性”和“强度”三个分值。
# A sentiment lexicon can be used to discern objective facts from subjective opinions in text.
# Each word in the lexicon has scores for:
# 1) polarity: negative vs. positive (-1.0 => +1.0)
# 2) subjectivity: objective vs. subjective (+0.0 => +1.0)
# 3) intensity: modifies next word? (x0.5 => x2.0)
一些直观的规则被硬编码在 TextBlob 中,以检测增加或减少句子整体极性得分的修饰语(如英语中的副词:“ very good ”)。在这篇博文中有关于这些规则的更详细的描述。
**情感评分:**要将 TextBlob(一个范围[-1,1]内的连续值浮点数)返回的极性评分转换为细粒度的类标签(一个整数),我们可以利用宁滨。在 Pandas 中使用pd.cut
函数很容易做到这一点——它允许我们通过在结果中所有 TextBlob 分数的浮动区间中使用相同大小的 bin,从连续变量变为分类变量。
**评估:**由于我们在训练和测试期间都在处理不平衡的类,所以我们关注宏 F1 分数(它是宏平均精度和召回率的调和平均值)以及分类准确度。可以看出,TextBlob 分类方法的准确度非常低,F1 分数也是如此。
混淆矩阵图显示了分类器预测最不正确的类别的更多详细信息。
Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
要阅读上述混淆矩阵图,请查看沿反对角线的单元。单元格[1,1]显示分类器正确预测的属于类别 1 的样本的百分比,单元格[2,2]显示正确的类别 2 预测的百分比,依此类推。远离反对角线的单元格显示了对每个相应类别做出的错误预测的百分比-例如,查看单元格[4,5],我们可以看到实际属于类别 5 的所有样本中有 47%被 TextBlob(不正确地)预测为类别 4。
很明显,我们的 TextBlob 分类器预测大多数样本为中性或轻度阳性,即类别 3 或 4,这解释了为什么模型精度如此低。很少有预测是强烈负面或正面的——这是有意义的,因为 TextBlob 对每个样本中的所有单词使用加权平均情感得分。这可以很容易地分散出单词之间极性差异很大的句子的效果,例如“这部电影是关于撒谎、欺骗,但爱你背叛的朋友
2 — VADER
“ValenceAwareDictionary and sEentimentReasoner”是另一个流行的基于规则的情感分析库。像 TextBlob 一样,它使用一个情感词典,其中包含基于人工标注标签的每个单词的强度测量。然而,一个关键的区别是,VADER 的设计侧重于社交媒体文本。这意味着它非常重视捕捉社交媒体上常见文本本质的规则——例如,带有表情符号的短句、重复的词汇和大量使用标点符号(如感叹号)。以下是 VADER 输出的情感强度分数的一些例子。
在上面的文本示例中,对同一个句子做了微小的改动。请注意,VADER 将情绪强度分数分解为积极、消极和中性成分,然后将其标准化并压缩到[-1,1]范围内作为“复合”分数。随着我们添加更多的感叹号、大写字母和表情符号,强度变得越来越极端(朝着+/- 1)。
**情感评分:**为了返回 SST-5 数据集上的离散类值,我们应用了与 TextBlob 类似的技术——通过 pandas pd.cut
函数使用宁滨将连续的“复合”极性得分(float)转换为离散值。这将为每个测试样本返回五个类中的一个,作为新列存储在结果数据帧中。
**评估:**上面使用的宁滨方法是将来自 VADER 的连续(浮点)值平均分成我们需要的五个离散类之一的一种相当粗糙的方法。然而,与 TextBlob 相比,我们确实看到了总体分类准确性和宏 F1 分数的提高。
VADER 的混淆矩阵显示了更多正确预测的类(沿着反对角线)-然而,关于对角线的不正确预测的传播也更大,给了我们一个更加“混乱”的模型。
Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
VADER 的更大传播(在反对角线之外)可以归因于这样一个事实,即它只给具有大写、lot的文本分配非常低或非常高的复合分数。由于 SST-5 实际上没有这样的注释文本(它与社交媒体文本有很大不同),因此该数据集的大多数 VADER 预测都在-0.5 到+0.5 的范围内(原始分数)。当转换为离散类标注时,这将导致更窄的分布,因此,许多预测可能会在真实标注的任一侧出错。
虽然 VADER 的结果准确度仍然很低,但很明显,与 TextBlob 相比,它基于规则的方法确实捕捉到了情绪的大量精细分级——很少真正负面的情况被归类为正面,反之亦然。
3 — 逻辑回归
从基于规则的方法向前移动,下一个尝试的方法是逻辑回归,这是最常用的分类监督学习算法之一。逻辑回归是一种基于标记数据的线性模型——术语线性很重要,因为它意味着算法仅使用输入和参数的线性组合(即和而非积)来产生类别预测。
Sebastian Raschka 在他的博客文章中对逻辑回归如何等同于一个非常简单的单层神经网络给出了一个非常简洁的解释。输入特征及其权重被输入到激活函数(用于二分类的 sigmoid,或用于多分类的 softmax)。分类器的输出只是 sigmoid/softmax 向量的索引,其中最高值作为类标签。
Source: Sebastian Raschka’s blog
对于多类逻辑回归,通常使用一对其余方法——在该方法中,我们训练 C 单独的二元分类模型,其中 C 是类的数量。每个分类器 f_c ,for c ∈ {1,…, C }被训练来预测样本是否属于类别 c 。
***将单词转换为特征:*要将文本转换为特征,第一步是使用 scikit-learn 的[CountVectorizer](https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html).
这将我们训练数据的整个语料库(即所有句子)转换为令牌计数矩阵。令牌(单词、标点符号等。)是使用 NLTK 的标记器创建的,常用的停用词如“a”、“an”、“the”被删除,因为它们不会给情感评分增加太多价值。接下来,计数矩阵被转换成 TF-IDF(术语频率逆文档频率)表示。来自 scikit-learn 文档:
Tf 表示术语频率,而 tf-idf 表示术语频率乘以逆文档频率。这是信息检索中常见的术语加权方案,在文档分类中也有很好的用途。使用 tf-idf 而不是给定文档中记号出现的原始频率的目的是按比例缩小记号的影响,这些记号在给定语料库中非常频繁地出现,因此在经验上比在一小部分训练语料库中出现的特征信息少。
***情感评分:*一旦我们获得了训练语料库的 TF-IDF 表示,就通过使其适合现有特征来训练分类器。“newton-cg
”解算器用于优化逻辑回归中的损失,默认情况下使用 L2 正则化。为每个测试样本返回一个情感标签(使用 scikit-learn 的learner.predict
方法),作为 softmax 输出向量中最大类别概率的索引。
***评估:*从基于规则的方法切换到基于特征的方法,总体分类精度和 F1 值都有显著提高,如下图所示。
然而,混淆矩阵显示了为什么在多类问题中查看整体准确性度量不是很有用。
Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
逻辑回归模型将大部分真实标签 1 和 5(强阴性/阳性)归类为属于它们的邻居类(2 和 4)。此外,几乎没有任何例子被正确归类为中性(第 3 类)。因为大多数训练样本属于类别 2 和 4,看起来逻辑分类器主要学习了* 在这些主要类别中出现的特征。*
4 —支持向量机
支持向量机(SVM)在如何优化损失函数以生成数据点之间的决策边界方面非常类似于逻辑回归。然而,主要的区别是“核函数”的使用,即,将复杂的非线性决策空间转换为具有更高维度的决策空间的函数,以便可以找到分离数据点的适当超平面。SVM 分类器使用将每个距离表征为向量的“支持向量”,来最大化每个数据点与该超平面的距离。
支持向量机的一个关键特征是它使用了一个铰链损失,而不是逻辑损失。这使得它对数据中的异常值更加稳健,因为铰链损失不会像逻辑损失那样迅速发散。
***训练和情感评分:*sci kit-learn 中的线性 SVM 是使用与前面描述的逻辑回归类似的管道建立的。一旦我们获得了训练语料库的 TF-IDF 表示,我们就通过使其适合训练数据特征来训练 SVM 模型。使用具有随机梯度下降(SGD)优化器的铰链损失函数,并且在训练期间应用 L2 正则化。情感标签作为 softmax 输出向量中最大类别概率的索引返回(使用 scikit-learn 的learner.predict
方法)。
***评估:*由于相当多的要素可能是现实数据集中的异常值,因此 SVM 在实践中产生的结果应该略好于逻辑回归。看看准确性和 F1 分数的提高,这似乎是真的。
与逻辑回归相比,优化器的选择与 SVM 模拟更复杂超平面的能力相结合,将样本分成各自的类别,从而略微改善了混淆矩阵。
Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
Side by side: Logistic Regression vs. SVM
SVM 模型预测强负/正类(1 和 5)比逻辑回归更准确。然而,它仍然不能预测足够多的样本属于类别 3——大部分 SVM 预测再次偏向主要的类别 2 和 4。这告诉我们,特性定义的方式还有改进的余地。与 TF-IDF 转换相结合的计数矢量器实际上并不了解任何关于单词如何相互关联的信息* —它们只是查看每个样本中单词共现的数量来做出结论。输入单词嵌入。*
5 —快速文本
FastText 是一个高效、可扩展、基于 CPU 的文本表示和分类库,由脸书人工智能研究(FAIR)团队于 2016 年发布。FastText 的一个关键特征是其底层神经网络学习表示,或嵌入*,即考虑单词之间的相似性。虽然 Word2Vec (一种更早于 2013 年发布的单词嵌入技术)做了类似的事情,但 FastText 有一些突出的关键点。*
- FastText 使用 n-gram 的集合来考虑子词:例如,“train”被分解为“tra”、“rai”和“ain”。以这种方式,单词的表示更能抵抗拼写错误和微小的拼写变化。
- FastText 可以更好地处理未知单词,因为它能够将长单词分解成子单词,这些子单词也可能出现在其他长单词中,从而提供更好的上下文。
***Python 模块:*虽然 FastText 的源代码是 C++,但是 2019 年 6 月 FAIR 还是发布了一个官方的 Python 模块(在社区内混乱了几个月后)。这使得完全在 Python 中训练和测试我们的模型非常方便,不需要使用任何外部二进制文件。但是,为了找到最佳的超参数,建议使用 FastText 的命令行界面。
***训练 FastText 模型:*要训练 FastText 模型,请使用[fasttext](https://fasttext.cc/docs/en/support.html#building-fasttext-as-a-command-line-tool)
命令行界面(仅限 Unix 这包含一个非常有用的超参数自动调整实用程序。根据文档,该实用程序针对最大 F1 分数优化所有超参数,因此我们不需要手动搜索特定数据集的最佳超参数。这是在终端上使用以下命令运行的,在 CPU 上大约需要 5 分钟。
Command to find optimum hyperparameters for FastText using the command line interface
上面的命令告诉 FastText 在训练集上训练模型,并在开发集上验证,同时优化超参数以实现最大 F1 分数。标志-autotune-modelsize 10M
告诉 FastText 优化模型的量化参数(如下所述),使得最终训练的模型大小小于 10 MB,并且启用-verbose
选项,以查看哪个超参数组合给出最佳结果。
💡提示:量化 FastText 模型: 量化通过使用 16 位或 8 位整数,而不是标准的 32 位浮点,减少了存储模型权重所需的位数。这样做极大地减小了模型的大小(几个数量级)。 FastText 使量化变得非常方便在其最新发布的命令行接口或其 Python 模块如下(量化模型的扩展是**.ftz**
,不是.bin
作为父模型)。根据超参数优化期间获得的值设置截止选项,这确保了最终模型大小保持在 10 MB 以下。
*# Quantize model to reduce space usage model.quantize(input=train, qnorm=True, retrain=True, cutoff=110539) model.save_model(os.path.join(model_path, "sst5.ftz"))*
下面的代码片段显示了如何使用最佳超参数在 Python 中训练模型(这一步是可选的,如果愿意,只能使用命令行训练工具)。
关于每个超级参数的含义以及 FastText 如何在幕后工作的更多细节,这篇文章给出了一个很好的描述。
***情感评分:*通过加载经过训练、量化的(.ftz
) FastText 模型来进行情感预测。该模型有一个predict
方法,根据从 softmax 输出层提取的概率输出最可能的标注。为了进行分类预测,我们简单地从这个概率列表中选择最可能的分类标签,直接将其提取为整数。
***评估:*可以看出,对于该数据集,FastText 模型的准确性和 F1 分数在 SVM 上没有显著提高。
然而,FastText 的 F1 分数略高于 SVM。
Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
两个模型并列的混淆矩阵更详细地强调了这一点。
Side by side: SVM vs. FastText
快速文本和 SVM 结果之间的关键区别是中性类的正确预测百分比,3。在大多数类别(2 和 4)中,SVM 比 FastText 正确地预测了更多的项目,这突出了基于特征的方法在不平衡类别的文本分类问题中的弱点。FastText 使用的单词嵌入和子单词表示法固有地给了它额外的上下文。在对未登录词进行分类时尤其如此,这在中性类中相当常见(尤其是只有一两个词的非常短的样本,大多是看不见的)。
然而,我们的 FastText 模型是使用单词三元模型训练的,因此对于中途改变极性的较长句子,该模型必然会“忘记”之前几个单词的上下文。RNN 或 LSTM 等序列模型将能够更好地捕捉更长期的背景,并对这种过渡情绪进行建模。
6 —天赋
2018 年, Zalando Research 发表了一个最先进的深度学习序列标记 NLP 库,名为 Flair 。这也很快成为分类任务的流行框架,因为它允许将不同种类的单词嵌入在一起,从而为模型提供更好的上下文感知。
Flair 的核心是一个名为字符串嵌入的上下文化表示。为了获得它们,来自大型语料库的句子被分解成字符序列,以预训练双向语言模型,该模型在字符级“学习”嵌入。通过这种方式,该模型可以学习区分大小写的字符(例如,发音相似的普通名词中的专有名词)和自然语言中的其他句法模式,这使得它对于命名实体识别和词性标注等任务非常强大。
Illustration of a BiLSTM sequence labeller with contextual character embeddings (Source)
训练用于分类的 Flair 模型:**使 Flair 极其方便而强大的是它能够用“Flair”(即字符串)嵌入来“堆叠”单词嵌入(如 ELMo 或 BERT)。以下示例显示了如何使用 Flair 嵌入实例化 BERT(基本,有大小写)或 ELMo(原始)嵌入的堆栈嵌入。堆叠表示被转换成嵌入的文档,即对整个文本样本进行单次嵌入(不管有多少个句子)。这允许我们将复杂的任意长度表示压缩为固定大小的张量表示,我们可以在 GPU 内存中进行训练。
以这种方式堆叠嵌入(BERT 或 ELMo)的能力来自于这样一个事实,即字符级字符串嵌入捕获潜在的语法语义信息 ,而不使用单词的概念(它们明确地专注于子单词表示),而来自外部预训练神经网络模型的堆叠单词嵌入给出了附加的单词级上下文。这增强了模型在给定文本中识别各种句法特征的能力,使其能够超越经典单词嵌入模型的性能。
关于训练的注意事项:Flair 模型需要一个 GPU 来进行训练,并且由于其 LSTM 架构与 transformer 架构相比不能高效地并行化,因此即使在这个相对较小的 SST-5 数据集上的训练时间也在几个小时的数量级。对于这个项目,运行了 25 个时期的训练,当训练停止时,验证损失仍在减少,这意味着模型相当不适合。因此,在现实世界的大型数据集上使用 Flair 进行分类任务可能会带来巨大的成本损失。
***情感评分:*和以前一样,评分技术是在 Pandas 现有的框架下实现的。首先加载训练好的模型,然后将文本转换成一个Sentence
对象(这是样本中每个句子的标记化表示)。调用 Flair 模型的predict
方法,使用 softmax 输出图层中的最大索引来预测类别标注,然后将最大索引提取为整数并按样本存储在 Pandas 数据帧中。因为即使在 GPU 上,模型推断也需要相当长的时间,所以实现了一个[tqdm](https://github.com/tqdm/tqdm/blob/master/examples/pandas_progress_apply.py)
进度条来显示模型完成了多少测试样本的预测。
***评估:*使用两个独立的堆叠表示来训练两个独立的模型——一个使用 BERT(基本、有壳),另一个使用 ELMo(原始)。使用每个模型进行推理,得出以下结果。
与快速文本和 SVM 模型相比,在准确性和 F1 分数方面都有相当大的提高!查看每种情况下的混淆矩阵,可以洞察哪些类别比其他类别预测得更好。
**
上面的图强调了为什么使用 BERT 嵌入的堆叠比使用 ELMo 嵌入的堆叠得分低得多。BERT 案例几乎没有对第 1 类做出正确的预测——但是它对第 4 类做出了更多正确的预测。ELMo 模型似乎与 Flair 嵌入堆叠得更好,并且为少数类(1 和 5)生成更大部分的正确预测。
Flair + BERT 模式在训练中哪里出了问题?可能是重新投影和减少隐藏维度的数量(在堆叠期间)导致预训练 BERT 模型的知识丢失,这解释了为什么该模型在强负样本上学习得不够好。与使用 BERT 进行堆叠相比,堆叠 ELMo 嵌入为什么会导致更好的学习,这一点并不十分清楚。然而,在这两种情况下,Flair 模型花费了大量时间(几个小时)来训练,这在现实世界中可能是一个巨大的瓶颈——然而,它们确实突出了使用上下文嵌入而不是经典单词嵌入来进行细粒度分类的能力。
结论
在这篇文章中,Python 中的六个不同的 NLP 分类器被用来在 SST-5 细粒度情感数据集上进行分类预测。通过逐步使用越来越复杂的模型,我们能够将 F1 的准确度和宏观平均分数提高到 48%左右,这还不算太差!在未来的帖子中,我们将看到如何使用迁移学习驱动的 transformer 模型来进一步提高这些分数。
Comparison of results: Fine-grained sentiment classification on SST-5
我们还能学到什么?
绘制归一化混淆矩阵给出了一些有用的见解,即为什么基于嵌入的方法的精度高于更简单的基于特征的方法,如逻辑回归和 SVM。很明显,在类不平衡的多类问题中,整体准确性是一个非常差的指标,比如这个问题,这就是为什么需要宏观 F1 分数来真正衡量哪些分类器性能更好。
机器学习模型(尤其是深度学习模型)的一个关键方面是,众所周知,它们很难解释。为了解决这个问题,我们将看一下 解释 我们的结果并回答这个问题:“为什么 X 分类器预测这个特定样本的这个特定类别?”。LIME Python 库用于这个任务,这将在下一篇文章中描述。
如果你一直读到这篇文章的结尾,感谢你的阅读!
- 这是 Python 中细粒度情感分析系列的第 1 部分。
- 第 2 部分 讲述了如何使用 LIME 构建一个解释器模块,并解释两个代表性测试样本上的类预测。
- 第三部分 讲述了如何通过建立我们自己的 transformer 模型,使用迁移学习,进一步提高准确率和 F1 分数。
***注意:*此分析的所有培训和评估代码都可以在项目的 Github repo 中获得,所以请随意复制结果并做出您自己的发现!
Python 中的细粒度情感分析(第 2 部分)
在本帖中,我们将使用 LIME 为各种细粒度情感分类结果生成解释
Source: Pixabay
“我为什么要相信你?”— 里贝罗等人
这是 Python 中细粒度情感分析系列的第 2 部分。第 1 部分讲述了如何用 Python 训练和评估各种细粒度的情感分类器。在这篇文章中,我们将讨论为什么一个分类器做出了一个特定的类别预测——也就是说,如何使用一种叫做 LIME 的流行方法来解释一个情感分类器的结果。
概括地说,以下六个模型用于在斯坦福情感树库(SST-5)数据集上进行细粒度情感类别预测。
- 基于规则的模型:文本块和 VADER
- 基于特征的模型:逻辑回归和支持向量机
- 基于嵌入的模型: FastText 和 Flair
使用线性过程来分析和解释使用每种方法的情感分类结果。
Sentiment classification: Training & Evaluation pipeline
以下部分解释了该过程的最后一步——为每种方法的预测生成解释。
局部可解释的模型不可知的解释
或者简单的说,石灰、**、*是以可解读的方式解释一个、*分类器的预测的一种技术原文。这里的术语“解释”是指生成文本或视觉辅助工具,如突出显示的图和图表,它们提供了对模型特征及其预测之间关系的定性理解。根据论文,以下要点使得 LIME 在解释复杂模型的分类结果时非常有效:
它是模型不可知的——它将原始模型视为黑盒,因此它可以应用于几乎任何分类器
它是局部忠实的 — 它对应于模型在被预测的实例附近的行为。因此,对于测试样本中给定的一组特征,解释对于这些特征所占据的决策空间是有意义的,这可能适用,也可能不适用于全局。
模型解释过程
为了给任何分类器提供解释,LIME 使用了一种非常聪明的方法-它从更大的黑盒分类器中唯一需要的是它对每个类的预测概率(在应用 softmax 函数后从模型的输出层)。下面的步骤简单地解释了这个过程。
- 生成文本样本的数千种变体,其中随机单词被删除,如下所示:
- 使用原始的、复杂的黑盒模型来为每个单独的变异生成类别概率和标签(其单词被部分删除)。例如,案例“It _ not terrible _ _ _ _”将具有类别概率
[0, 0, 0.6, 0.3, 0.1]
(即,它将属于类别 3),而“It _ terrible _ _ _ _”将具有概率[0.9, 0.1, 0, 0, 0]
并属于类别 1。 - 针对每个变化,在由黑盒模型预测的标签上训练更小、更简单的线性模型。
- 观察简单线性模型中最重要的特征(这更容易解释)
- 最后,基于特征重要性生成单词的每个单词(或标记)的可视化
解释如何作用于多个类?
对于多类文本分类,如 SST-5 数据集,LIME 使用预测概率,通过 one-vs-rest 方法突出每个特征(即标记)对预测类的影响。来自 LIME 的多类解释示例如下所示。模型预测这个句子的类别为 2。正在解释的文本中突出显示的颜色是随机生成的,颜色越深表示该标记对于预测类别的特征重要性越大(在下面的示例中为“沉闷”)。
Prediction class probabilities and feature importance plots generated by LIME
解释器类
正如上一篇文章一样,面向对象的方法被应用于尽可能重用代码。这个[项目的 GitHub repo explainer.py ]中提供了解释器的所有代码。定义了一个 Python 类,该类接受由 LIME 生成的变量列表(标记空白的随机文本样本),并将每个样本的类概率输出为一个 numpy 数组。
一旦每个变化的类概率被返回,这可以被馈送到LimeTextExplainer
类。启用词包(bow
)意味着 LIME 在生成变体时不考虑词序。然而,FastText 和 Flair 模型分别考虑 n 元语法和上下文排序进行训练,因此为了模型之间的公平比较,SST-5 上的所有解释都禁用了bow
标志选项。
LIME explainer 返回的exp
对象是一个内部方法,将局部线性模型的预测(以数值形式)转换为人类可以理解的可视化形式,输出为 HTML 文件。
破解基于规则的方法
由于基于规则的方法(文本块和 VADER)不输出类别概率(它们只输出单个情感分数),为了使用 LIME 解释它们的结果,我们必须为这些方法人工生成类别概率。虽然这不是一个正式的过程,但使用连续值情感分数(在范围[-1, +1]
内)模拟类概率的一个简单的变通方法是将浮动分数归一化到范围[0, 1]
内,然后通过将其大小缩放 5 倍将其转换为离散整数类。对文本块和 VADER 的操作如下。
此外,基于规则的模型输出一个且仅一个预测,因此为了避免输出其他类的零概率,由 5 个点组成的正态分布(其平均值作为预测的整数类)用于将小的非零概率分配给剩余的类。下图显示了这是如何做到的。这个模拟概率数组现在可以被 LIME 用来生成基于规则的模型的解释。
Example of simulated probabilities for rule-based classifier scores
每个分类器关注什么?
为了理解预测,为六个训练好的分类器中的每一个运行文件explainer . py——这输出了一个带有可视内容的 HTML 文件,帮助我们解释模型的特征理解。
从上一篇文章中完成的 EDA,我们知道类 1 和 3 是 SST-5 数据集中的少数类,所以从这两个类中选择两个样本。
不可怕,只是平庸得可怕。 (真实标签 )
全体演员都很优秀……但这部电影本身只是稍微有点魅力。 (真题 3)
这些样本中的每一个都包含修饰语、相互冲突的词汇和句子中快速变化的极性,所以原则上,它们应该有助于揭示每个分类器关注的是什么。
文本块
***例一:*不是恐怖,只是平庸得可怕。
True: 1 — Predicted: 3
对于句子“不可怕,只是平庸得可怕”,在单词“可怕”之前使用否定项使 TextBlob 相信该项目并不可怕——然而,句子“只是平庸得可怕”的第二分句重申了该项目确实非常平庸的事实。这个句子作为一个整体仍然是压倒性的负面,使真正的标签为 1,但 TextBlob 重点关注否定术语,以推动情绪评级上升到 3。
例 2:演员阵容都很出色……但这部电影本身只是有点迷人。”
True: 3 — Predicted: 5
上面这句话真的很有挑战性,因为前半句是压倒性的正面,后半句是相当负面的——使得整个句子在情绪上是中性的。单词“轻度”、“迷人的”和“优秀的”在很大程度上导致 TextBlob 错误地将此句子分类为强阳性(标签 5)。一般来说,强阳性词的多次出现倾向于推动 TextBlob 中基于规则的算法将句子整体分类为阳性。
VADER
True: 1 — Predicted: 5
在上面的例子中,VADER 预测的情绪等级与预期完全相反。VADER 倾向于重罚负极性词汇(如“可怕的”),但也重奖负极性词汇之前的否定术语(“ 而非 可怕的”)。在句子的后半部分出现“极其平庸的”没有任何影响,因为前半部分强有力的积极得分超过了后面的一切——在这个例子中,VADER 基于规则的方法被证明有点太聪明了。
True: 3 — Predicted: 5
再一次,VADER 基于规则的方法很重视像“优秀的”和“迷人的”这样听起来积极的词,从而错误地给这个句子贴上了标签 5。单词“仅仅是”和“轻度”应该会降低句子的整体得分,却被 VADER 忽略了——这很可能是因为这些单词没有出现在 VADER 的情感词典或基于规则的修饰语中。
逻辑回归
True: 1 — Predicted: 2
逻辑回归似乎确实学习了修改句子整体情感评级的个体特征(标记)——正如上面可以看到的,单词“只是”和“一般”对标签 2 的贡献更大,而“可怕的”和“可怕的”对“不是 2”(大概是标签 1)的贡献更大。标签 1 和 2 的预测概率几乎相等,因此在这种情况下,基于特征的方法对标签 2 的误差非常小。
True: 3 — Predicted: 3
在第二个例子中,很明显,单词“但是“”、“轻微的”和“仅仅是”被逻辑回归模型正确地识别为将整个句子情感摆动到中性状态。
支持向量机
True: 1 — Predicted: 1
与逻辑回归不同,SVM 更侧重于强烈的负面词汇“可怕的”和“可怕的”来给这个词贴上 1 的标签。否定词“ not ”和单词“平庸”被正确地识别为将评级推离 1——但它们的效果很低,总体情绪标签仍然是 1。
True: 3 — Predicted: 3
在第二个例子中,很明显,SVM 学会了使整个句子中性的正确特征。单词“仅仅是”、“轻度”和“但是”降低了句子被分配标签 2、4 和 5 的概率,使得标签 3 最有可能。
快速文本
True: 1 — Predicted: 1
FastText 主要关注示例 1 中的强否定词,其他类别标签的概率为零。它确实得到了正确的整体预测,但这可能只是因为 FastText 了解到“可怕的”是一个经常出现在强烈否定句中的单词。
True: 3 — Predicted: 3
在示例 2 中,FastText 模型正确预测中性标签的原因更加清楚。由于该模型是使用单词三元模型和 5 的上下文窗口训练的(关于训练参数,请参见上一篇文章),该模型在进行预测时会查看标记的序列。例如,单词“一致优秀的”紧接着是“但是”,单词“仅仅是温和的”紧接在“迷人的”之前,因此查看可视化中突出显示的单词,模型知道使该句子中性的单词共现(而不是单个单词)的序列。
Flair + ELMo
True: 1 — Predicted: 2
True: 3 — Predicted: 2
Flair + ELMo 嵌入模型对两个例子的预测都有很大的误差。在上述两种可视化中,模型似乎对周期给予了很高的权重。)标记——其他分类器则不是这样。在任何一种情况下,预测标签的概率都非常接近正确标签,因此该模型在如何从数据中学习方面似乎是正确的。
需要注意的重要一点:在训练阶段,Flair + ELMo 模型不适合,即验证损失甚至在 25 个时期的训练后仍在减少(参见本系列的第 1 部分)——这意味着进一步的训练可以将分类器推向这些和其他示例的正确概率输出。
分析
通过研究各种方法的解释结果,我们可以观察到一些优点和缺点,以及关键变量对每种方法性能的影响。
强极性词的效应
TextBlob 和 VADER 倾向于对极性强的单词进行加权,即使有其他极性较弱的单词(或否定项)会改变整个句子的情绪。硬编码的规则在许多情况下工作得很好,但是现实世界中的自然语言有太多的可变性,这些规则在实践中很难工作得很好,至少对于细粒度的情感分析来说是这样。
基于嵌入的方法对涉及强极性单词的情况有最好的处理。FastText 模型是用三元模型训练的,所以它学会了拾取强极性单词之前或之后的单词序列,所以它不容易被愚弄,与基于规则和基于特征的方法相比,它可以在中性类上做出更好的预测。Flair 模型,由于其上下文嵌入和强大的底层语言模型,能够更准确地识别涉及强极性单词序列的模式。
句子长度的影响
当使用基于规则的方法时,长样本,尤其是多句子样本可能会带来麻烦,这种方法倾向于应用某种加权平均来捕捉整个句子的极性。因此,句子越长,就越有可能分散长样本中单个子句的实际情感。
非常短的(单个或两个单词)样本对模型提出了不同的挑战——它们要么包含看不见的单词,要么提供的上下文太少,如标点符号或模型可以用来对单词进行分类的类似子词。一般来说,基于规则的模型在这些情况下会很糟糕,因为缺少修饰语(“ very ”或“ too ”)不能在我们的细粒度分类场景中提供足够的情感强度概念。
看不见的词的效果
在有大量未见过的单词的样本中,或者在非常短的样本中,一个单词在训练中未见过的可能性非常高,缺乏顺序(n-gram)或上下文表示(嵌入)的模型往往难以做出可靠的细粒度情感预测。scikit-learn 中基于特征的模型非常清楚地展示了这个问题。由于它们依赖于训练过程中的单词共现计数,如果一个看不见的单词出现在测试集中,该单词将被基于特征的分类器忽略。这错过了可能赋予模型更多上下文的特性。
FastText 由于其字符 n-gram 子词表示而更好地处理未见过的词,但它没有 Flair 模型那样深入的预训练表示,以正确处理未见过的词。Flair + ELMo 嵌入模型使用来自预训练词汇表的上下文表示(10 亿字的新闻抓取数据)。这使它比较浅的模型具有显著的优势——上下文化的嵌入携带有用的信息,可以识别意思相似的单词及其子单词表示。
模型成本(训练和/或推理)
所有基于规则的方法都涉及零训练时间,并且在预测阶段非常快,但是这是以不可见的真实世界数据的结果缺乏稳定性为代价的。由于 scikit-learn 中高效的矢量化表示,基于特征的方法也可以非常快速地进行训练和推断。然而,这些模型也有局限性,不能捕捉单词之间的关系,并且对看不见的单词处理不好。
FastText 是计算成本(由于其底层 C++和 Cython 绑定,它非常快)和分类准确性之间的一个很好的折衷。对于细粒度的情感分析,必须使用三元模型表示来训练模型,以便捕捉长句中出现的单词序列中涉及的更精细的层次。
Flair 是所有选项中最昂贵的选项,主要是因为它是一个大型深度学习模型,使用来自字符串和单词嵌入组合的预训练表示。训练该模型可能需要几个小时(或几天,取决于数据集的大小)。Flair + ELMo 模型在训练和推理过程中速度较慢的部分原因是在运行时间期间查找 ELMo 嵌入的方式——这可以使用小批量来加速——然而,现实情况是,Flair 这样的模型在真实世界的数据集上很可能被证明过于昂贵,无法进行快速有效的推理。
用于模型测试的交互式仪表板
为了更容易地看到对许多不同示例的模型范围的解释,使用 Flask 微框架创建了一个仪表板。使用 Heroku ( 以类似于本例的方式部署仪表板。为了交互式地测试仪表板,输入一个文本样本并选择我们想要解释其分类预测的分类器,如下所示。
试试这里的仪表盘,做出你自己的解释!
Try out the dashboard! https://sst5-explainer.herokuapp.com
结论
在这篇文章中,我们讨论了如何使用 Python 中的六个不同的分类器来生成和解释细粒度情感的石灰解释。每个模型所关注的特征的差异是显而易见的,这使得为手头的任务选择正确的分类器变得更加简单。词汇的底层表示越复杂(尤其是上下文嵌入),SST-5 数据集的五个情感类别的模型预测就越可靠。
到目前为止,SST-5 数据集的准确度/F1 分数如下:
虽然基于 Flair + ELMo 嵌入的模型确实达到了 48.9%的相当不错的准确度,但这仍然离最先进的水平(64.4%)非常远。此外,训练和预测 Flair 模型的成本非常昂贵,因此需要一个计算效率更高,但又能感知上下文的模型。
在本系列的 第 3 部分 中,我们将看到如何使用带有迁移学习的 transformer 模型来进一步改进这些结果。感谢阅读!
承认
- 生成 LIME 解释的代码借用了 Adam Geitgey 的这篇精彩文章。如果你想深入了解石灰的工作原理,请读一读吧!
- Python/Flask dashboard 是一个快速而肮脏的实现,它的代码受到了这个优秀的 Flask 教程的启发。
密码
- 所有进行情绪预测和生成石灰解释的代码都在这个项目的 GitHub repo 中。
- 烧瓶前端应用程序的所有代码都可以在这个单独的 repo 中获得。
细粒度情感分析(第 3 部分):微调变压器
PyTorch 中使用预训练变压器的动手迁移学习
Source: Pixabay
这是 Python 中细粒度情感分析系列的第 3 部分。第 1 部分和第 2 部分涵盖了对斯坦福情感树库细粒度(SST-5)数据集上六种不同分类方法的分析和解释。在这篇文章中,我们将看看如何通过建立一个基于 transformer- 的模型和应用迁移学习来改善过去的结果,这是一种最近已经统治 NLP 任务排行榜的强大方法。
从本系列的第一篇文章中,以下分类准确度和 F1 分数是在 SST-5 数据集上获得的:
在下面的章节中,我们将讨论关键的培训、评估和解释步骤,这些步骤说明了为什么 transformers 比上面列出的方法更适合这项任务。
是什么让变形金刚如此强大?
transformer 架构的核心是以下关键思想,这些思想使其非常适合解释自然语言中的复杂模式:
- 自我关注:这是一种机制,变形金刚用它来表示输入序列的不同位置,并学习它们之间的关系。
- 多头注意:变形金刚将自我注意的任务委托给多个“头”,即从不同的表征子空间共同注意来自序列中不同位置的信息。这允许他们使用无监督学习有效地扩展(大型数据集)。
- 迁移学习:最近领先的基于 transformer 的方法是通过迁移学习实现的——即使用从之前的设置(例如无监督训练)中提取的知识,并将其应用于不同的设置(例如情感分类或问题回答)。这是通过两个阶段的过程实现的:预调整,随后是微调,或适应。
Source: “The State of Transfer Learning in NLP” by Sebastian Ruder
本质上,最近所有基于 transformer 的顶级方法( GPT 、伯特、 XLNet )都使用这种顺序迁移学习方法。在内部,他们在预训练阶段使用来自大型语料库的未标记数据训练一个语言模型,随后一个额外的特定任务模块(附在语言模型的下游)根据定制数据进行微调。下面是这种方法在多个基准数据集上的可视化。
Source: Devlin et al. 2019
预培训目标的重要性
预训练步骤包括以无监督的方式训练语言模型——这决定了模型如何从给定的训练语料库中学习语法、语义和上下文信息。最近的证据(来自 OpenAI 的 GPT-2 等模型)表明,如果有足够的数据和计算,真正大型的语言模型将会学到很多关于语言语法的知识!对于本文后面描述的 transformer 模型,下面的预处理目标值得注意。
- OpenAI GPT 在预训练过程中使用了一个从左到右语言建模目标——也就是说,它学习从左到右预测序列中最有可能的下一个单词,就像它们在自然语言中出现一样(至少对于英语来说)。这种模型通常被称为经典语言模型,或者 因果 语言模型。这里的“因果”一词指的是这样一个事实:一个标记出现在特定位置的可能性是由出现在它之前的标记序列引起的。
- 伯特在其核心使用一个屏蔽语言模型,通过在预训练期间随机屏蔽一个序列的 15%的标记获得——这允许模型学习如何预测在前一个标记之前或之后出现的标记(它被双向训练*,不像 GPT)。除了屏蔽之外,BERT 还使用了下一句预测目标,该模型学习预测一句话是否在前一句话之后出现。与因果语言建模相比,这种方法需要较长的训练时间,因为屏蔽一部分标记会导致较小的训练信号。*
- XLNet 使用置换语言建模目标——与 BERT 不同,它在预训练期间随机屏蔽序列中的每个标记(不仅仅是 15%)。通过这种方式,模型学习预测两个方向上序列的随机记号*,允许模型学习记号之间的依赖性(不仅仅是在给定序列中哪些记号最有可能)。可以想象,这在训练时间方面更加昂贵,并且需要更大的语言模型来获得良好的基线。*
在这篇文章的其余部分,我们将使用一个使用因果语言建模目标(类似于 GPT/GPT-2,但比它小得多)训练的变压器模型。
制造变压器
有了这些背景知识,我们可以继续写一些代码了!
读取数据集
使用 Pandas 对 SST-5 数据集进行了一些基本的预处理。注意,类标签递减 1(在范围[0,1,2,3,4]内),因为 PyTorch 期望标签是零索引的。
对数据进行令牌化和编码
处理数据的第一步是使用单词块标记化器进行标记化— [ 参见本文第 4.1 节了解更多详情]。我们使用 HuggingFace 的 [pytorch_transformers](https://huggingface.co/pytorch-transformers/model_doc/bert.html?highlight=berttokenize#pytorch_transformers.BertTokenizer)
库中实现的BertTokenizer
。接下来,标记化的文本被编码成整数序列,由我们的 transformer 模型处理。随后,创建一个 PyTorch [DataLoader](https://pytorch.org/docs/stable/data.html#torch.utils.data.DataLoader)
将样本装载到批次中进行训练。
请注意,在本例中,我们将序列的最大长度固定为 256——理论上,BERT 和类似模型编码器可以处理的最大序列长度为 512,但由于 SST-5 是一个样本相对较短的相对较小的基准数据集,我们将最大令牌序列长度截断为 256,以减少内存使用和模型大小。
一个特殊的分类标记‘[CLS]’
被添加到每个序列的末尾——这个标记在分类任务中被用作每个序列表示的集合,以了解该序列属于哪个类。对于短于 256 的序列,添加一个填充标记‘[PAD]’
以确保所有批次在训练期间保持相同的大小加载到 GPU 内存中。
构建变压器模型
用于分类任务的变压器组的一般结构如下所示。这是 Vaswani 等人用于机器翻译的原始版本的修改形式。
Source: Child et al.
在 PyTorch 代码中,上面的结构如下所示。
定义了一个从 PyTorch 的nn.module
继承而来的基类Transformer
。输入序列(在我们的例子中,是用于情感分类的文本)通过对序列的标记和位置嵌入求和而被馈送到变换器块。每个连续的变压器组由以下模块组成:
- 层标准化:本质上,标准化应用于训练期间的小批量,以使优化更容易(从数学上讲)——与未标准化的网络相比,这提高了模型的性能。层标准化允许通过改变维度来选择任意大小的小批量,在该维度上为每个批量计算批量统计数据(平均值/方差)——经验表明,这可以提高递归神经网络(RNNs)中顺序输入的性能。要获得图层规范化的直观解释,请阅读 Keita Kurita 的这篇博客文章。
- 自我关注:使用 PyTorch 的
MultiHeadAttention
模块封装变形金刚的自我关注逻辑——即变形金刚对位置信息进行编码并在训练过程中从中学习的能力。 - 漏失:神经网络中减少过拟合的经典正则化技术——这是通过在训练过程中引入随机噪声来实现的。
- 前馈模块:执行前向传递,包括另一层归一化,带有一个隐藏层和一个非线性(通常为 ReLU 或 GELU ),然后是 dropout。
在我们网络的前馈模块中定义了两个“掩码”。
- 填充掩码:这屏蔽了之前引入的填充标记(
[‘PAD’]
),使每个批次的每个序列长度相同。这样做告诉模型在推断过程中屏蔽掉这些标记,以便它们被自我关注模块忽略。填充遮罩特定于每个批次。
Padding mask: NAACL 2019 transferlearning tutorial slides
- 注意屏蔽:由于该方法使用因果语言模型,所以包括了注意屏蔽——这按照因果语言模型屏蔽了下面输入中的先前标记。通过将主对角线以上的所有元素的值设置为负无穷大,使用上三角矩阵(
[torch.triu](https://pytorch.org/docs/stable/torch.html#torch.triu)
)为所有批次指定相同的掩码。
Attention mask: NAACL 2019 transferlearning tutorial slides
添加分类标题
模型的下游部分在现有转换器的顶部使用线性分类层。TransformerWithClfHead
类继承自基础Transformer
类,并将CrossEntropyLoss
指定为要优化的损失函数。线性层的大小为[embedding_dimensions, num_classes]
—在这种情况下,对于现有的预训练模型和 SST-5 数据集,为 410×5。
从分类层中提取原始输出,即逻辑值,并将其提供给 softmax 函数,以生成分类概率向量(1×5)作为输出。
训练模型
从由 HuggingFace、提供的[预训练模型初始化模型的权重,并且在 SST-5 数据集上运行训练脚本 [training/train_transformer.py](https://github.com/prrao87/fine-grained-sentiment/blob/master/training/train_transformer.py)
。
以下超参数用于训练模型——注意嵌入维度的数量、注意头的数量等。没有显式设置—这些是从预训练模型继承的。在 3 个训练时期之后,对模型进行检查点检查,并保存其配置参数。
💡线性预热时间表:来自 [pytorch-ignite](https://pytorch.org/ignite/contrib/handlers.html#ignite.contrib.handlers.param_scheduler.PiecewiseLinear)
的分段线性时间表不是设置一个恒定的学习速率,而是定义为在训练的早期阶段提高学习速率,然后线性下降到零。这通常是一种在迁移学习过程中确保知识良好迁移的好方法(类似于 ULMFiT,Howard and Ruder,2018 )中的“倾斜三角形学习率”。
💡梯度累积:正如 Thomas Wolf 在他的文章“ 训练神经网络的实用技巧 ”中描述的那样,模拟较大批量而不会遇到 GPU 内存问题的一个好方法是累积梯度。这是通过对来自多个反向传播步骤的梯度张量求和,然后调用优化器使损失最小化来实现的——注意,损失还需要除以累积步骤的数量。这样做可以让我们用比 GPU 内存中实际容纳的更大的批量进行训练,从而改善模型的学习。**
模型推理
在本节中,我们将通过一个示例,逐行深入研究如何使用一个经过训练的模型对我们的 SST-5 情感数据集进行推断。输入文本被标记化,转换成整数 id,并作为适当形状的张量馈送给模型,如下面的笔记本所示。
这个过程使用文件[classifiers.py](https://github.com/prrao87/fine-grained-sentiment/blob/master/classifiers.py)
中的TransformerSentiment
类封装,以将 SST-5 数据集读入 Pandas 数据帧,并评估转换器的性能。
估价
在 SST-5 测试上运行经过训练的 transformer 模型(在这个 Google drive 链接中可用),我们可以看到因果 transformer 将分类准确率提高了近 50%!
transformer 的 macro-F1 分数也比其他基于嵌入的方法(FastText 和 Flair)有所提高。
Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
下一个最好的模型 Flair 的混淆矩阵被放在变压器的混淆矩阵旁边进行比较。
转换器确实做出了许多属于类别标签 2 和 4 的错误预测,但是,与所有其他方法相比,它获得了更多属于少数类别(1 和 3)的正确标签。它能够用如此有限数量的训练样本合理地分类这些少数类的事实证明了使用预训练语言模型和迁移学习进行情感分类任务的能力。
解释变压器的预测
按照本系列的第 2 部分中所示的模型解释方法,我们使用我们训练好的 transformer 模型对 SST-5 测试集中的特定文本示例进行 LIME explainer 可视化。用于生成以下可视化效果的代码可在文件[explainer.py](https://github.com/prrao87/fine-grained-sentiment/blob/master/explainer.py)
中获得——经过训练的模型文件也可在这个 Google drive 链接中获得。
***例一:*不是恐怖,只是平庸得可怕。
True: 1 — Predicted: 1
变压器似乎正确地将单词“可怕的”和“一般的”识别为促成该示例具有类别标签 1(即,强烈否定)的两个最重要的特征。而非这个词将预测概率推离 1(大概是为了标注 2 或 3),但其效果没有句子中的否定词那么强。副词“可怕地”在将这个句子归类为强烈否定中也起了很小的作用,这意味着该模型对副词等修饰语如何改变句子中的情感程度有所了解。
例 2:演员阵容都很出色……但这部电影本身只是有点迷人。
True: 3 — Predicted: 3
在此示例中,很明显,单词“ but ”在变压器对类别标签 3(中性)的预测中具有最大的权重。这很有意思,因为虽然在这句话中有很多词表现出不同程度的极性(“优秀的”、"仅仅是"、“温和的"和"迷人的”),但是"但是"这个词在这句话中充当了一个关键的修饰语——它标志着句子在后半部分从强烈肯定到轻微否定的过渡。再一次,转换者似乎知道修饰语(在这种情况下是连词)如何改变句子的整体情感程度。
另一个有趣的观察是单词“ cast ”在被预测为中性的情绪(类别 3)中不起作用。**所有之前使用的方法(参见第 2 部分),包括 Flair,都错误地将单词“ cast 的特征重要性学习为有助于情感——因为这个单词在训练数据中多次出现。转换器的因果语言模型的潜在功能有助于它在单词和预测的情感标签之间建立更有意义的关联,而不管该单词在训练集中出现了多少次。****
结束语
通过这个系列,我们探索了 Python 中的各种 NLP 方法,用于在斯坦福情感树库(SST-5)数据集上进行细粒度分类。虽然这个数据集极具挑战性,并给现有的文本分类方法带来了许多问题,但很明显,与其他方法相比,NLP 中的当前行业标准——结合迁移学习的 transformers 在分类任务上表现出内在的优异性能。这主要是由于转换器的底层语言模型表示,这给予它更多的上下文意识和对训练词汇的更好的句法理解。
下图总结了 SST-5 数据集上准确性和 F1 分数的连续改进(使用越来越复杂的模型)。
附加实验
为了从转换器中挤出更多的性能,可以尝试一些额外的实验(除了增加模型中的参数数量之外):
- 特定领域数据:目前的方法使用因果语言模型(即 HuggingFace 的预训练模型),该模型有 5000 万个可训练参数。如 NAACL 教程中所述,该模型在 Wikitext-103(即来自维基百科文章的 1.03 亿个标记)上进行了预训练。用更相关的特定领域数据(例如几十万条电影评论)来增加预训练可以帮助模型更好地理解 SST-5 中发现的典型词汇。这一步虽然很昂贵——因为它需要重新训练语言模型——但它是一次性的步骤(,可以使用 HuggingFace 的存储库中的
[pretraining_train.py](https://github.com/huggingface/naacl_transfer_learning_tutorial)
文件来完成),并且可以在分类准确性方面产生显著的下游改进。 - 掩蔽语言建模:使用因果语言模型作为预训练目标,虽然训练成本更低,但可能不是获得高精度分类结果的最佳方式。将掩蔽语言建模作为预训练目标进行实验(同时用电影评论增加训练数据)可以产生更准确的语言模型。事实上, NAACL 教程表明,与因果语言模型相比,使用掩蔽语言建模目标的预训练产生了更低的复杂度,这意味着该模型可以在情感分类等下游任务中表现得更好。
- 超参数调整:试验更大的批量(用于训练和测试)、增加梯度累积步骤和查看不同的学习率预热时间表可以在测试集上产生额外的性能增益。
如本系列所述,从头开始构建情感分析框架的目的是建立对我们能力的信心,即评估和解释不同的机器学习技术,与我们的问题陈述和可用数据相关。当然,利用大型预训练的基于 transformer 的模型,如 BERT-large、XLNet 或 RoBERTa,可以显著提高真实数据集的性能——然而,重要的是平衡使用这些模型的巨大计算成本与使用具有良好预训练目标和干净、良好注释的、领域特定的训练数据的更小、更简单的模型。随着未来几个月越来越多的 NLP 技术在变形金刚的巨大成功上展开,未来只会有有趣的时代!
代码和训练模型
- 使用 transformer 模型对 SST-5 进行训练和预测的代码在这个项目的 GitHub repo 中。
- 经过训练的变形金刚模型可以在这个 Google drive 链接中找到。
请随意使用 transformer 重现结果并做出您自己的发现!
承认
这篇文章使用的所有代码都是从以下链接中的优秀示例代码改编而来的:
- 用于 NAACL 2019 迁移学习 NLP 教程的 GitHub 资源库
- Google Colab 笔记本展示变形金刚模型实验
- 本文由 Oliver Atanaszov 在变压器微调上发表
进一步阅读
为了更详细地深入了解 NLP 中的迁移学习,强烈建议浏览 Sebastian Ruder、Matthew Peters、Swabha Swayamdipta 和 Thomas Wolf 的 NAACL 2019 教程幻灯片。
使用自定义语料库上的预训练来微调 Albert
Photo by Esperanza Zhang on Unsplash
使用特定领域文本的自定义语料库对 Albert 进行预训练,并针对应用任务进一步微调预训练模型
介绍
这篇帖子展示了在自定义语料库上预训练最先进的 Albert[1] NLP 模型,并在特定的下游任务上进一步微调预训练的 Albert 模型的简单步骤。自定义语料库可以是特定领域或外语等,我们没有现有的预训练阿尔伯特模型。下游任务是应用驱动的,它可以是文档分类问题(例如,情感分析),或者标记问题(例如,NER)等。这篇文章展示了一个自定义实体识别下游任务。本文中的示例用例是提取餐馆评论中的菜名,例如,在评论“我非常喜欢马拉汽船!”中,将“马拉汽船”标记为菜名
然而,艾伯特模型的细节和它是如何工作的并没有在这篇文章中讨论。这篇文章假设你对变形金刚()和阿尔伯特模型有粗略的了解,并且能够从 git repos 克隆和运行脚本。
我的包含插图笔记本的 Github repo 可以在这里找到:https://Github . com/LydiaXiaohongLi/Albert _ fine tune _ with _ pre train _ on _ Custom _ Corpus。我在同一个 repo 中包含了一个 colab 笔记本,它演示了玩具数据集的 E2E 步骤,包括构建 vocab、预训练 Albert 和微调 Albert,由于简单的数据和训练所需的较少步骤,它适合 colab 的 CPU。
玩具数据集
本帖中使用的玩具数据集包括:
- 餐厅评论语料库:在这个玩具例子中由两个评论句子组成,用于预训练艾伯特模型。在实际应用中,一般我们可以用 100M+的句子进行训练。
Restaurant Review Corpus
2.从餐馆评论中提取菜名以微调 Albert 进行菜名识别。训练集由两个相同的复习句子组成,提取菜名。
Dish Name Extraction — Train Set
评估集由两个相似的评论句子组成,但是两个评论中的菜肴交换了,并且文本上下文略有不同,包含未收录的单词(不在餐馆评论语料库中的单词),以验证 Albert 模型的效率。
Evaluation Set
系统模型化
构建 Vocab
第一步是为餐馆评论语料库建立词汇。
Google 并没有开源 WordPiece unsupervised tokenizer,但是,我们可以在开源的 SentencePiece 或 t2t text encoder 子词生成器上进行修改,以生成与 Bert/Albert 模型兼容的词汇。
修改 t2t 文本编码器子字生成器的开源实现可以在 https://github.com/kwonmha/bert-vocab-builder【2】找到
- 克隆 repo(创建一个环境并相应地安装需求)
- 准备语料库文件,在本例中是餐馆评论语料库
- 运行下面的命令来创建 Albert 兼容的词汇文件
python subword_builder.py --corpus_filepattern “{corpus_for_vocab}” --output_filename {name_of_vocab} --min_count {minimum_subtoken_counts}
预先训练艾伯特
下一步是用自定义语料库对 Albert 进行预训练。官方阿尔伯特回购可以在https://github.com/google-research/ALBERT找到
克隆回购
创建一个环境并相应地安装需求。
创建艾伯特预培训文件
使用在构建 Vocab 步骤中使用/创建的自定义语料库和词汇文件运行以下命令。该步骤首先基于输入的词汇文件对语料库进行标记和编码。然后,它为 Albert 创建训练输入文件,以在掩蔽语言模型和句子顺序预测任务上进行训练。
python create_pretraining_data.py --input_file “{corpus}” --output_file {output_file} --vocab_file {vocab_file} --max_seq_length=64
预训练艾伯特模型
- 准备 Albert 配置文件,Albert 配置文件中的 vocab_size 应该与构建 vocab 步骤中创建的 vocab 文件中的词汇总数相同。另一个示例 Albert 配置文件可以在 https://tfhub.dev/google/albert_base/2 的找到,这是用于构建 Albert base v2 模型的配置文件。
- 运行以下命令开始预训练。所有的模型参数将被存储在 output_dir 中。(下面的玩具模型仅运行 300 步的预训练,批量大小为 2)
python run_pretraining.py --input_file={albert_pretrain_file} --output_dir={models_dir} --albert_config_file={albert_config_file} -- train_batch_size=2 --num_warmup_steps=100 --num_train_steps=300 --max_seq_length=64
预训练中的 max_seq_length 应与创建预训练数据步骤相同,以便 tensorflow TFRecord 正确解析数据。
如果您的数据很大,并且存储在 GCS 桶中,您可以相应地添加 TPU 相关参数,如下所示。
python run_pretraining.py — input_file=”gs://{bucket_name}/{folder_names}/{albert_pretrain_file_name}” — output_dir=”gs://{bucket_name}/{folder_names}” — albert_config_file=”gs://{bucket_name}/{folder_names}/albert_config_file” — use_tpu=True — tpu_name={tpu_name} — tpu_zone={tpu_zones} — num_tpu_cores=8
对于实际应用,我们应该有更大的训练批量、训练步数和预热步数。因此,最初的 Albert 论文使用 4096 批量和 125k 训练步数,预热步数为 3125。在这里,我们可以使用谷歌的免费 300 美元启动一个 TPU(v2–8)进行训练,如果只使用一个 v2–8 TPU,你应该用更小的批量(例如 512)和更多的训练步骤进行训练,否则可能会遇到 OOM 问题。
或者,在样本 E2E colab 笔记本中,我用 pytorch 重写了训练前的步骤。
微调艾伯特
最后一步是微调预训练的 Albert 来执行菜名识别任务。参考笔记本了解该步骤。这篇文章中的代码片段并没有连接到完整的笔记本,它们只是为了说明的目的。如果不清楚,请阅读完整的笔记本。
使用 huggingface 的变形金刚实现的 pytorch 版本的微调步骤,可以在同一个 repo 中找到,参考这个笔记本。
流程输入
为了获得统一长度的输入序列,我们设置了 50 个标记的最大长度,如果超过,则截断该序列,否则填充该序列。为此,我们可以使用 Keras 的 pad_sequences。
Albert models 采用 input _ ids[batch _ size * max _ seq _ len]—标记化和编码的文本序列(在这种情况下,input _ mask[batch _ size * max _ seq _ len]—如果标记是根据原始序列编码的,则给出 1;如果填充,则仅根据原始序列的标记计算损失;segment _ ids[batch _ size * max _ seq _ len]—零张量数组(在这种情况下不使用,labels[batch _ size * max _ seq _ len]—如果标记是菜肴名称 0 的一部分,则给出 1;否则。我们将以这种格式处理输入。
df_data[‘text_tokens’] = df_data.text.apply(tokenizer.tokenize)
df_data[‘text_labels’] = df_data.apply(lambda row: label_sent(row[‘name’].lower().split()
, row[‘text_tokens’]), axis=1)
df_data_sampled = df_data[[np.sum(label)>0 for label in df_data.text_labels]]
input_ids = pad_sequences([tokenizer.convert_tokens_to_ids(txt) for txt in df_data_sampled[‘text_tokens’]],
maxlen=MAX_LEN, dtype=”long”, truncating=”post”, padding=”post”)
labels = pad_sequences(df_data_sampled[‘text_labels’],
maxlen=MAX_LEN, padding=”post”,
dtype=”long”, truncating=”post”)
# create the mask to ignore the padded elements in the sequences.
input_mask = [[int(i>0) for i in ii] for ii in input_ids]
批量输入张量。
d = tf.data.Dataset.from_tensor_slices(
({“input_ids”: tf.constant( input_ids, shape=[num_examples, seq_length], dtype=tf.int32),
“input_mask”: tf.constant( input_mask, shape=[num_examples, seq_length], dtype=tf.int32),
“segment_ids”: tf.zeros(shape=[num_examples, seq_length], dtype=tf.int32),}
,tf.constant(labels, shape=[num_examples, seq_length], dtype=tf.int32),))
d = d.batch(batch_size=batch_size, drop_remainder=drop_remainder)
创建阿尔伯特微调模型
在预训练的艾伯特模型之后增加一层,以预测输入序列中的每个标记是否是菜名的一部分。
def create_model(albert_config, mode, input_ids, input_mask, segment_ids,labels, num_labels):
“””Creates a classification model.”””
is_training = mode == tf.estimator.ModeKeys.TRAIN
model = modeling.AlbertModel(
config=albert_config,
is_training=is_training,
input_ids=input_ids,
input_mask=input_mask,
token_type_ids=segment_ids)
output_layer = model.get_sequence_output()
hidden_size = output_layer.shape[-1].value
output_weight = tf.get_variable(
“output_weights”, [num_labels, hidden_size],
initializer=tf.truncated_normal_initializer(stddev=0.02))
output_bias = tf.get_variable(
“output_bias”, [num_labels], initializer=tf.zeros_initializer())
在这种情况下使用对数丢失。
logits = tf.matmul(output_layer, output_weight, transpose_b=True)
logits = tf.nn.bias_add(logits, output_bias)
logits = tf.reshape(logits, [-1, MAX_LEN, num_labels])log_probs = tf.nn.log_softmax(logits, axis=-1)
one_hot_labels = tf.one_hot(labels, depth=num_labels, dtype=tf.float32)
per_example_loss = -tf.reduce_sum(one_hot_labels * log_probs, axis=-1)
loss = tf.reduce_sum(per_example_loss)
带有权重衰减的 Adam 优化器在这种情况下用于学习
列车微调模式
train_op = optimization.create_optimizer(
total_loss, learning_rate, num_train_steps, num_warmup_steps,
use_tpu, optimizer)output_spec = tf.contrib.tpu.TPUEstimatorSpec(
mode=mode,
loss=total_loss,
train_op=train_op,
scaffold_fn=scaffold_fn)train_input_fn = input_fn_builder(
file = “data_toy/dish_name_train.csv”,
tokenizer = tokenizer,
seq_length=MAX_LEN,
drop_remainder=True)estimator.train(input_fn=train_input_fn, max_steps=num_train_steps)
在这一步中,如果你没有用 TPU 估算器训练,它将自动检测没有 TPU 可用,并降级到 CPU。
评估微调模型
报告损失和准确性以供评估
def metric_fn(per_example_loss, label_ids, logits):
predictions = tf.argmax(logits, axis=-1, output_type=tf.int32)
accuracy = tf.metrics.mean(tf.math.equal(label_ids,predictions))
loss = tf.metrics.mean(values=per_example_loss)
#
return {
“eval_accuracy”:accuracy,
“eval_loss”: loss,
}
通过 100 个时期的训练(在笔记本中,批量大小被设置为 1,并且训练步长被设置为 200,这对于 100 个时期的训练是有效的),该模型能够在评估集中提取正确的菜肴名称,即使上下文文本与甚至超出词汇表的单词略有不同
Prediction on Evaluation Set
结论
简而言之,Albert 是 NLP 的一大突破,由于开源,我们可以非常直接地在最先进的模型上构建定制的应用程序。
参考
[1] 钟真 L 。,明达 C 。、塞巴斯蒂安 G、凯文 G 。,皮尤什年代。,拉杜 S 。,用于语言表示的自我监督学习的 Lite BERT(2019),谷歌研究
[2]托马斯·w、弗拉达利·d、维克多·s、朱利安·c、克莱门特·d、安东尼·m、皮尔里奇·c、蒂姆·R、雷米·l、摩根·f、杰米·b,《拥抱脸的变形金刚:最先进的自然语言处理》(2019 年)
[3] M. H. Kwon, Bert-Vocab-Builder ,Github Repo
[4]塞伯坦 AI,羔羊优化器,Github Repo