细粒度图像分割(FGIS)
内部人工智能
图像抠图 vs SOD vs 软分割
问题和计算机视觉的拯救
如今,照片的真实编辑需要仔细处理自然场景中经常出现的颜色混合。这些颜色混合通常通过场景或对象颜色的软选择来建模。因此,为了实现高质量的图像编辑和背景合成,图像区域之间的这些软过渡的准确表示是至关重要的。目前行业中用于生成这种表示的大多数技术严重依赖于由熟练视觉艺术家进行的某种用户交互。因此,创建如此精确的显著性选择成为一项昂贵而乏味的任务。为了填补熟练视觉艺术家的这一空白,我们利用计算机视觉来模拟人类视觉系统,人类视觉系统具有有效的注意力机制来确定视觉场景中最显著的信息。这种类型的问题也可以解释为前景提取问题,其中显著对象被认为是前景类,而剩余场景是背景类。计算机视觉和深度学习旨在通过一些选择性的研究分支来模拟这种机制,即图像抠图、显著对象检测、眼睛注视检测和软分割。还需要注意的是,与计算机视觉不同,深度学习主要是一种数据密集型的研究方法。
随着最近全卷积网络(FCN)用于图像分割的使用增加,深度学习已经显著改善了前景提取和显著性检测基线。尽管有所有这些改进,大多数建议的体系结构使用最初为图像属性分类任务设计的网络主干,其提取具有语义意义的代表性特征,而不是全局对比度和局部细节信息。但在这篇博客中,我们将把这个问题留到我以后的博客中更详细地讨论。
是细分问题吗?
是的,如果从我们输出格式的角度来看,这是一个分段问题。近年来,语义分割已经成为计算机视觉和深度学习领域的一个关键问题。因此,着眼于更大的场景,我们可以说语义分割是其领域中的关键任务之一,为更好的场景理解铺平了道路。越来越多的应用从图像和视频中推断认知事实,这也突出了场景理解的重要性。
我们讨论的三种实现平滑且感觉良好的细粒度语义分割的方法是:
- 图像抠图
- 显著目标检测
- 软分割
图像抠图
图像抠图可以理解为绿屏抠像的一般化版本,用于在不受约束的设置中精确估计前景不透明度。图像抠图在计算机图形学和视觉应用中都是一个非常重要的课题。早期的图像抠图方法涉及大型稀疏矩阵,例如大型核抠图拉普拉斯算子及其优化。然而,求解这种线性系统的这些方法通常非常耗时,并且不受用户欢迎。许多研究试图通过使用自适应核大小和 KD 树来提高该线性系统的求解速度,但是在野生图像的质量和推理速度方面没有观察到显著的提高。因为问题是高度不适定的,所以用户通常给出指示明确前景、明确背景和未知区域的三分图(或笔画)作为支持输入。
一个自然图像抠图捕捉非常精细的细节的例子,比如头发— 来源
让我们首先制定一个图像抠图的基本方程。将图像像素的背景色、前景色和前景不透明度(α遮罩)分别表示为 B、F 和α,像素的颜色 C 可以写成 B 和 F 的凸组合:
C = F(α)+B(1α)。
图像抠图方法可以分为三种主要类型,基于传播的、基于采样的和基于学习的。在一些方法中,还使用基于采样和基于传播的抠图的混合组合。
基于采样的图像抠图基于这样的假设,即未绘制像素的真实背景和前景颜色可以从位于未知像素附近的已知背景和前景像素中导出。一些基于采样的方法有:
基于传播的图像遮片技术通过将已知的局部背景和前景像素的阿尔法值传播到未知区域来计算未绘制像素的阿尔法值。然而,在野生背景图像的情况下,对颜色知识的过度依赖导致图像中背景和前景颜色分布重叠的假象。一些基于传播的方法有:
然而,基于采样和传播的技术都不能提供令人满意的和完全自动化的结果。因此,最近,几项深度学习研究已经提出了通过将三分图和 RGB 图像级联输入到 FCN 中来解决上述线性系统或者仅 RGB 图像本身来预测最终阿尔法遮片的方法。一些已知的依赖于 trimap 的深度学习架构是:
而一些独立于 trimap 的深度学习架构是:
用于自动图像抠图的注意力引导深度网络— 来源
根据我的个人经验,基于深度学习的方法能够比其他两种方法更好地捕捉全局语义信息和局部细节,并且它们不偏向于已知和未知区域像素之间存在相关性的任何粗略假设。
显著目标检测
SOD 的主要目标是分割图片中最显著(重要)和视觉上最有吸引力的对象。在图像分割和视觉跟踪等许多领域,SOD 有着广泛的应用。与图像抠图类似,在用于显著性检测的全卷积网络(FCN)兴起后,SOD 的发展水平有了显著提高。
SOD 模型的理想显著图示例— 来源
与自然图像抠图不同,显著对象检测并不像看起来那么复杂。实现精确显著目标检测的主要挑战是:
①显著性定位。特定视觉资产的显著性通常定义在整个图像的全局对比度上,而不是任何逐像素或局部特征上。因此,为了实现精确的 SOD,显著性检测算法不仅必须捕获整个图像的全局对比度,还必须建立前景对象的细节结构的精确表示。为了解决这个问题,使用了多级深度特征聚合网络。
(2) **没有边界细化损失。**用于训练显著性对象检测模型的最常见损失是联合交集(IoU)损失或交叉熵(CE)。但是这两种方法都导致了模糊的边界细节,这是由于它们没有有效地区分边界像素。许多研究也使用骰子点数损失,但它的主要目的是处理有偏差的训练集,而不是专门加强精细结构的建模。
研究历史
关于显著对象检测的深度学习文献有丰富的现代历史。一些研究强调使用具有注意力机制的深度循环网络对一些选择性图像子区域进行迭代细化。另一方面,一些研究强调了通过深层多路径循环连接将全球信息从网络的深层转移到浅层的有效性。许多作者,如胡等人[1]和王等人[2]提出了使用递归全连通网络或递归级联多层深度特征进行显著对象检测的方法。这些研究也显示了预测误差迭代校正的有效性。与之前提到的研究工作相反,一些研究还显示了在 U-Net 架构中使用上下文注意力网络来预测像素式注意力图。这些提取的逐像素注意力图在评估度量方面被证明对于显著性检测非常有效。很少提出的方法强调从粗略预测到精细预测的过渡。这些方法提出了通过捕捉更精细的结构来实现更精确的边界细节的细化策略。例如,Lu 等人提出了一种架构,该架构捕捉用于对显著性图的各种全局结构化显著性线索以及后细化阶段进行建模的深度分层显著性表示。最近发表的显著物体检测领域的进展(在我写这篇博客的时候)是由秦等人提出了一个具有两级嵌套 u 型结构的强大深度网络架构()。作者陈述的关键改进是多尺度上下文信息捕获(感受域的混合)和增加的网络深度(在剩余 U 块中汇集),而没有任何显著的计算开销。
显著目标检测结果显示了空间分布的有效性— 来源
根据我的个人经验,SOD 在自然图像抠图方面也实现了较高质量的显著图,但在透明度建模和精细结构提取方面质量较差。
软分割
软分割被定义为将图像分解成两个或更多个部分,其中每个成员像素可以属于两个或更多个部分。
语义软段,通过为每个段分配
纯色来可视化— 源
研究历史
大多数早期的软分割方法强调使用逐像素颜色分解或全局优化来提取各种同质颜色的软显著图。虽然观察到这些提取的柔和色彩图对于许多关键的图像编辑应用(例如图像重新着色)是有用的,但是与 SOD 类似,它们不特别考虑对象边界和过渡区域粒度。有趣的是,图像抠图与软分割的分支有非常密切的关系。事实上,一些图像抠图文献(如抠图拉普拉斯算子)完全符合软分割的关键思想,即捕捉图像中局部软过渡区域的强大表示。给定一组用户定义的区域,这些方法主要基于迭代求解两层软分割问题以生成多层的思想。Levin 等人关于频谱抠图的工作也通过经由频谱分解自动估计一组空间连接的软片段来服务于相同的目的。最近由 Aksoy 等人进行的软分割研究也遵循了结合光谱分解和遮片拉普拉斯算子的光谱遮片的思想。然而,与光谱抠图不同,他们的工作从光谱分解的角度解决问题,通过融合局部纹理信息和来自为场景分析训练的深度卷积神经网络的高级特征。他们的主要贡献之一是使用图状结构,通过语义对象以及它们之间的软转换来丰富相应拉普拉斯矩阵的特征向量。
(a)抠图拉普拉斯算子,(b)语义拉普拉斯算子和©两者一起应用的结果— 来源
以我个人的经验,软分割是自然图像抠图的一个衍生分支,结合了丰富的历史图像抠图实践和深度学习的力量。也不同于普通的图像抠图,软分割给出了表示语义上有意义的区域的更多输出层。但是,尽管有这些显著的改进,仍有很大的改进空间需要解决。
结论
我已经从解决显著前景提取问题的角度解释了这些方法,但是这些方法旨在解决的实际问题在它们各自的研究分支中是非常多样和丰富的,并且以它们的方式对深度计算机视觉(我对计算机视觉+深度学习)的领域做出了贡献。
这篇博客给出了所有这些方法的概述,从一个研究者的角度来说,我们甚至没有恰当地触及这些主题的表面。要阅读关于抠图的详细内容,请查看 AlphaNet,我可能会在未来的博客中更深入地讨论这些话题。
参考
[1],,,傅志荣,和冯江恒.用于显著目标检测的递归聚集深度特征。美国路易斯安那州新奥尔良市 AAAI-18 会议录,第 6943-6950 页,2018 年。
[2],王,,陆沪川,,阮向军.基于递归完全卷积网络的显著目标检测。2018 年 IEEE 模式分析与机器智能汇刊。
微调一个非英语 GPT-2 模型与拥抱脸
微调非英语,德国 GPT-2 模型与德国食谱拥抱脸。使用它们的训练器类和管道对象
原载于 2020 年 9 月 6 日https://www . philschmid . de。
介绍
除非你生活在岩石下,否则你可能听说过 OpenAI 的 GPT-3 语言模型。你可能也看过所有疯狂的演示,其中模型编写了JSX
、HTML
代码,或者它在零/少量学习领域的能力。西蒙·奥里甘写了一篇文章,里面有基于 GPT 3 的优秀演示和项目。
GPT-3 的一个缺点是它有 1750 亿个参数,这导致模型大小约为 350GB。相比之下,GPT-2 迭代的最大实现有 15 亿个参数。这小于 1/116 的大小。
事实上,由于有接近 175B 的可训练参数,GPT-3 在尺寸上比其他任何型号都要大得多。这里是最近流行的 NLP 模型的参数数量的比较,GPT-3 明显突出。
这一切都很壮观,但是你不需要 1750 亿个参数就能在text-generation
中获得好的结果。
已经有关于如何微调 GPT-2 的教程了。但是很多都过时了。在本教程中,我们将在最新版本(3.1.0)中使用 Huggingface 的transformers
库。我们将使用新的Trainer
级,并用来自 chefkoch.de 的德国配方对我们的 GPT-2 模型进行微调。
你可以在这个 colab 笔记本上找到我们正在做的一切。
变形金刚库由 Huggingface
变形金刚库为自然语言理解(NLU)和自然语言生成(NLG)提供最先进的机器学习架构,如 BERT、GPT-2、罗伯塔、XLM、DistilBert、XLNet、T5。它还提供了 100 多种不同语言的数千个预训练模型,并可在 PyTorch & TensorFlow 2.0 之间深度互操作。它使开发人员能够针对不同的 NLP 任务(如文本分类、情感分析、问答或文本生成)微调机器学习模型。
辅导的
在教程中,我们从 Huggingface 模型中枢微调一辆德国 GPT-2。作为数据,我们使用德国食谱数据集,它由 12190 个德国食谱组成,元数据从 chefkoch.de 中抓取。
我们将使用食谱指导来微调我们的 GPT-2 模型,并让我们在事后编写我们可以烹饪的食谱。
在本教程中,我们使用带有 GPU 运行时的 Google Colab。如果你不确定如何使用 GPU 运行时,看看这里的。
我们要做什么:
- 从 Kaggle 加载数据集
- 准备数据集并构建
TextDataset
- 用
TrainingArguments
和 GPT-2 模型初始化Trainer
- 训练并保存模型
- 测试模型
你可以在这个 colab 笔记本里找到我们做的一切。
从 Kaggle 加载数据集
正如在教程介绍中已经提到的,我们使用 Kaggle 的"德国食谱数据集"数据集。该数据集由 12190 个德国食谱组成,元数据从 chefkoch.de 抓取而来。在这个例子中,我们只使用食谱的说明。我们使用“下载”按钮下载数据集,并将其上传到我们的 colab 笔记本,因为它只有 4,7MB 的压缩大小。
上传文件后,我们使用unzip
提取recipes.json
。
您也可以使用 *kaggle*
CLI 来下载数据集,但请注意,您需要在 colab 笔记本中保存您的 Kaggle 凭据。
这是一个食谱的例子。
准备数据集并构建一个TextDataset
下一步是从所有食谱中提取说明,并构建一个TextDataset
。TextDataset
是变形金刚库实现的 Pytroch [Dataset](https://pytorch.org/tutorials/beginner/data_loading_tutorial.html#dataset-class)
类的自定义实现。如果你想在 Pytorch 中了解更多关于Dataset
的信息,你可以看看这个 youtube 视频。
首先,我们将recipes.json
分成一个train
和test
部分。然后我们从食谱中提取出Instructions
并将它们写入train_dataset.txt
和test_dataset.txt
下一步是下载标记器。我们使用来自german-gpt2
模型的标记器。
现在我们可以建造我们的TextDataset
。因此,我们用tokenizer
和数据集的路径创建了一个TextDataset
实例。我们还创建了我们的data_collator
,它用于训练从我们的数据集形成一个批处理。
用TrainingArguments
和 GPT-2 模型初始化Trainer
训练器类为全功能训练提供了一个 API。Huggingface 的大多数示例脚本中都使用了它。在实例化我们的Trainer
之前,我们需要下载我们的 GPT-2 模型并创建训练参数。TrainingArguments
用于定义超参数,我们在learning_rate
、num_train_epochs
或per_device_train_batch_size
等训练过程中使用这些超参数。你可以在这里找到完整的列表。
训练并保存模型
为了训练模型,我们可以简单地运行trainer.train()
。
训练完成后,您可以通过调用save_model()
保存模型。这将把训练好的模型从我们的TrainingArguments
保存到我们的output_dir
。
测试模型
为了测试这个模型,我们使用了变形金刚库的另一个亮点pipeline
。管道是提供简单 API 的对象,专用于几个任务,text-generation
等等。
结果:
第一次做大足,2 分钟做大足。森林在河边倒下了。去死吧。黄油三明治。他的头发也是这样,比你的头发还长。”
嗯,就是这样💫。我们做到了👨🏻🍳。我们已经成功地微调了我们的 gpt-2 模型来为我们编写食谱。
为了改善我们的结果,我们可以训练它更长时间,并调整我们的TrainingArguments
或扩大数据集。
你可以在这本 colab 笔记本里找到一切。
感谢阅读。如果你有任何问题,随时联系我或评论这篇文章。你也可以通过 Twitter 或 LinkedIn 与我联系。
用定制语料库的预训练来微调 Albert 来自真实应用程序训练的一些笔记和打嗝
图片来自互联网海底捞汽船
这是我上一篇文章的后续文章,这篇文章展示了使用玩具数据集对 Albert 进行预训练的详细步骤。在这篇文章中,我将分享我自己在实际应用中应用这个模型的经验。我的应用程序是一个象征性的分类,Albert 在包含 2 亿多句子的外语(非英语)的 chats 语料库上进行了预训练。
预培训
创建训练前文件
您需要遵循 Albert 的“创建预训练文件”脚本所要求的输入格式,即不同文档之间一行,文档中不同句子之间一行。
如果您直接在 200+M 的句子上运行创建预训练文件的脚本,将会花费很长时间。因此,我把文集分成 200 多个文件,每个文件有 1 百万行。对于每个 1M 行的文件,在 CPU 上运行大约需要 2 个小时,最终创建的文件大约需要 4.5G 空间。我在分布式机器上使用并行运行的批处理作业来创建所有文件(我公司现有的基础设施)
预训练设置
我使用谷歌的免费 300 信用点进行预训练。继https://cloud.google.com/tpu/docs/quickstart之后,我设置了一辆 v2–8 TPU。为了避免 OOM,我将批量减少到 512(默认为 4096)。我已经将预训练的所有数据放入 vocab 文件,并将生成的预训练模型检查点也存储在 GCS 中。我已经将训练输出控制台保存到日志文件中,以便稍后我可以从日志文件中提取训练损失。
预训练参数
由于聊天句子往往很短,因此我将最大长度设置为 64(默认为 512)。因为我已经减少了批量大小,所以我相应地线性增加了训练步骤(到 1Mil,缺省值是 125000)和预热步骤(到 25000,缺省值是 3125)。我已经线性地降低了学习率(到 0.00022,缺省值是 0.00176,虽然学习率和批量大小不是线性关系,但是 0.00176 这个很好的缺省值正好可以除以 8,所以我就这样设置了,😛,但是反正对我来说是有效的)。我将 save_checkpoints_steps 设置为较小的最小值 1000,希望减少内存使用。
初始化
我没有对 Albert 模型进行随机初始化,而是使用 tf-hub 的预训练 Albert v2 base(英语,并且在撰写本文时,只有英语预训练模型可正式用于 Albert)权重进行初始化。一些打嗝是:
- 来自 tf-hub 的模型没有 adam_m 和 adam_v 变量,我们必须手动添加它们
- tf-hub 模型中的变量以前缀“module/”存储,我们必须删除它才能使用 Albert repo 的训练前脚本
- 我们还需要手动添加 glonal_step 变量(要求是 int64 类型)
参考这个笔记本。
预训练结果
训练损失从大约 4+开始,在 300k 训练步数后稳定到大约 1.8,此后在该范围内波动,直到 700k 步数,因此我在 700k 步数时提前停止(为了节省一些积分:P)。我尝试了另一个更低的学习率(0.00005),损失减少到 2.7 左右,没有进一步发展。这确实因情况而异,我在网上看到一些训练损失稳定在 0.3 范围,而其他稳定在> 3。
完成 70 万步需要大约 2-3 天的时间,几乎用光了 300 美元的积分。
微调
我在之前的帖子中已经提供了两个微调笔记本,一个用于 tensorflow,一个用于 pytorch(使用 huggingface 团队的变形金刚回购),实际应用中使用的是 pytorch 版本。
微调设置
我用了 8 个 CPU,30 GB 内存+ 2 个英伟达特斯拉 T4 GPU。
微调参数
在我的标记分类微调任务(二元分类,要么标记是期望短语的一部分,要么不是)中,我用 500k+的训练样本进行了训练,其中有 50%的负样本(即对于负样本,整句中没有期望的标记,类比于餐馆评论玩具样本是,评论句子中没有菜名)。用 5 个 epoches 训练,学习率为 0.000001,批量大小为 32,具有完全微调的选项,即更新艾伯特模型以及最终微调 FCL,使用来自所有样本的所有表征的平均交叉熵损失。
微调结果
我能够达到大约 94%的验证准确率(所有样本中准确分类的令牌总数),同样,这个数字没有意义,并且确实因情况而异。但总的来说,它对我的应用程序是有用的。
使用连指手套微调手套嵌入
2013 年后,单词嵌入甚至在 NLP 社区之外也变得非常流行。 Word2vec 和 GloVe 属于静态单词嵌入家族。然后是一系列的动态嵌入伯特,ELMO,罗伯塔,阿尔伯特,XLNET…所有这些嵌入都依赖于语境词。在这篇文章中,让我们看看如何微调静态嵌入。
您是否曾经遇到过这样的情况:当您拥有一个非常小的数据集,并且想要应用静态单词嵌入时,却面临以下问题:
- 预训练模型中不存在数据集词汇
- 无法从数据集中训练整个模型,因为它太小
解决方案是加载预先训练好的模型,并使用来自数据集的新数据对它们进行微调,这样,看不见的词汇也被添加到模型中。
为什么不能微调 word2vec:
Gensim 是 word2vec 使用最多的库,微调这些嵌入有一些问题。新数据集中词汇的嵌入将在不对旧嵌入进行任何改变的情况下被训练。这导致预训练嵌入和新嵌入之间的差异。
fasttext 也不提供微调功能。
微调手套
Mittens 是一个用于微调手套嵌入的 python 库。这个过程包含 3 个简单的步骤。加载预训练的模型,建立新数据集的共生矩阵,并训练新的嵌入。
加载预训练模型
Mittens 需要将预训练的模型作为字典加载。所以,让我们做同样的事情。从https://nlp.stanford.edu/projects/glove获得预训练模型
def glove2dict(glove_filename):
with open(glove_filename, encoding='utf-8') as f:
reader = csv.reader(f, delimiter=' ',quoting=csv.QUOTE_NONE)
embed = {line[0]: np.array(list(map(float, line[1:])))
for line in reader}
return embedglove_path = "glove.6B.50d.txt"
pre_glove = glove2dict(glove_path)
数据预处理
在构建单词的共现矩阵之前,让我们对数据集做一些预处理。
sw = list(stop_words.ENGLISH_STOP_WORDS)
brown_data = brown.words()[:200000]
brown_nonstop = [token.lower() for token in brown_data if (token.lower() not in sw)]
oov = [token for token in brown_nonstop if token not in pre_glove.keys()]
我们已经使用布朗语料库作为样本数据集,并且 oov 表示未在预训练手套中出现的词汇。共生矩阵是从 oov s 构建的。它是一个稀疏矩阵,需要 O(n^2).的空间复杂度因此,有时为了节省空间,必须过滤掉真正罕见的单词。这是一个可选步骤。
def get_rareoov(xdict, val):
return [k for (k,v) in Counter(xdict).items() if v<=val]oov_rare = get_rareoov(oov, 1)
corp_vocab = list(set(oov) - set(oov_rare))
如果需要,删除那些罕见的oov,并准备数据集
brown_tokens = [token for token in brown_nonstop if token not in oov_rare]
brown_doc = [' '.join(brown_tokens)]
corp_vocab = list(set(oov))
构建共生矩阵:
我们需要单词-单词共现,而不是通常的术语-文档矩阵。sklearn 的 CountVectorizer 将文档转化为 word-doc 矩阵。矩阵乘法Xt*X
给出单词-单词共现矩阵。
cv = CountVectorizer(ngram_range=(1,1), vocabulary=corp_vocab)
X = cv.fit_transform(brown_doc)
Xc = (X.T * X)
Xc.setdiag(0)
coocc_ar = Xc.toarray()
微调连指手套型号
要安装连指手套,请尝试pip install -U mittens
查看完整文档了解更多信息。只需实例化模型并运行 fit 函数。
mittens_model = Mittens(n=50, max_iter=1000)
new_embeddings = mittens_model.fit(
coocc_ar,
vocab=corp_vocab,
initial_embedding_dict= pre_glove)
将模型保存为 pickle 供将来使用。
newglove = dict(zip(corp_vocab, new_embeddings))
f = open("repo_glove.pkl","wb")
pickle.dump(newglove, f)
f.close()
这是完整的代码。
感谢您阅读这篇文章。欢迎通过 Github 、 Twitter 和 Linkedin 联系我。干杯!
来源: 1。https://github.com/roamanalytics/mittens
2。https://surancy . github . io/co-occurrence-matrix-visualization
用变压器微调 BERT 模型
设置自定义数据集,用 Transformers Trainer 微调 BERT,并通过 ONNX 导出模型
这篇文章描述了一个开始微调变压器模型的简单方法。它将涵盖基础知识,并向您介绍来自transformers
库的惊人的Trainer
类。你可以从 Google Colab 运行代码,但是不要忘记启用 GPU 支持。
我们使用从新冠肺炎公开研究数据集挑战赛构建的数据集。这项工作是一个更大的项目的一小部分,该项目是建立 cord19 搜索应用。
安装所需的库
!pip install pandas transformers
加载数据集
为了微调 cord19 应用程序的 BERT 模型,我们需要生成一组查询文档特征和标签,以指示哪些文档与特定查询相关。在本练习中,我们将使用query
字符串表示查询,使用title
字符串表示文档。
training_data = read_csv("https://thigm85.github.io/data/cord19/cord19-query-title-label.csv")
training_data.head()
有 50 个唯一的查询。
len(training_data["query"].unique())50
对于每个查询,我们都有一个文档列表,分为相关(label=1
)和不相关(label=0
)。
training_data[["title", "label"]].groupby("label").count()
数据分割
为了便于说明,我们将使用一个简单的数据划分为训练集和验证集。即使我们在考虑独特的查询和文档对时有超过 5 万个数据点,我相信这个特定的案例将受益于交叉验证,因为它只有 50 个包含相关性判断的查询。
from sklearn.model_selection import train_test_split
train_queries, val_queries, train_docs, val_docs, train_labels, val_labels = train_test_split(
training_data["query"].tolist(),
training_data["title"].tolist(),
training_data["label"].tolist(),
test_size=.2
)
创建 BERT 编码
创建训练和验证编码。为此,我们需要选择使用哪个 BERT 模型。我们将使用填充和截断,因为训练例程期望一批中的所有张量具有相同的维数。
from transformers import BertTokenizerFast
model_name = "google/bert_uncased_L-4_H-512_A-8"
tokenizer = BertTokenizerFast.from_pretrained(model_name)
train_encodings = tokenizer(train_queries, train_docs, truncation=True, padding='max_length', max_length=128)
val_encodings = tokenizer(val_queries, val_docs, truncation=True, padding='max_length', max_length=128)
创建自定义数据集
现在我们有了编码和标签,我们可以创建一个Dataset
对象,如变形金刚网页中关于自定义数据集的描述。
import torch
class Cord19Dataset(torch.utils.data.Dataset):
def __init__(self, encodings, labels):
self.encodings = encodings
self.labels = labels
def __getitem__(self, idx):
item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
item['labels'] = torch.tensor(self.labels[idx])
return item
def __len__(self):
return len(self.labels)
train_dataset = Cord19Dataset(train_encodings, train_labels)
val_dataset = Cord19Dataset(val_encodings, val_labels)
微调 BERT 模型
我们将使用BertForSequenceClassification
,因为我们试图将查询和文档对分为两个不同的类别(不相关、相关)。
from transformers import BertForSequenceClassification
model = BertForSequenceClassification.from_pretrained(model_name)
我们可以将所有基本模型参数的requires_grad
设置为False
,以便仅微调特定于任务的参数。
for param in model.base_model.parameters():
param.requires_grad = False
然后我们可以用Trainer
微调模型。下面是一个带有一组现成参数的基本例程。选择下面的参数时应该小心,但这超出了本文的范围。
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir='./results', # output directory
evaluation_strategy="epoch", # Evaluation is done at the end of each epoch.
num_train_epochs=3, # total number of training epochs
per_device_train_batch_size=16, # batch size per device during training
per_device_eval_batch_size=64, # batch size for evaluation
warmup_steps=500, # number of warmup steps for learning rate scheduler
weight_decay=0.01, # strength of weight decay
save_total_limit=1, # limit the total amount of checkpoints. Deletes the older checkpoints.
)
trainer = Trainer(
model=model, # the instantiated 🤗 Transformers model to be trained
args=training_args, # training arguments, defined above
train_dataset=train_dataset, # training dataset
eval_dataset=val_dataset # evaluation dataset
)
trainer.train()
将模型导出到 ONNX
一旦训练完成,我们可以使用 ONNX 格式导出模型,以部署到其他地方。下面我假设你有一个 GPU,比如你可以从 Google Colab 获得。
from torch.onnx import export
device = torch.device("cuda")
model_onnx_path = "model.onnx"
dummy_input = (
train_dataset[0]["input_ids"].unsqueeze(0).to(device),
train_dataset[0]["token_type_ids"].unsqueeze(0).to(device),
train_dataset[0]["attention_mask"].unsqueeze(0).to(device)
)
input_names = ["input_ids", "token_type_ids", "attention_mask"]
output_names = ["logits"]
export(
model, dummy_input, model_onnx_path, input_names = input_names,
output_names = output_names, verbose=False, opset_version=11
)
结束语
如前所述,这篇文章涵盖了基本的培训设置。这是一个需要改进的良好起点。最好从简单的开始,然后补充,而不是相反,尤其是在学习新东西的时候。我将超参数调优、交叉验证和更详细的模型验证等重要主题留到后续文章中。但是有一个基本的训练设置是一个很好的第一步。
为任何摘要任务微调 T5 变压器
用数据做很酷的事情!
借用自 pexels.com的免版税图片
介绍
我对 T5 变压器型号的威力感到惊讶!T5 代表文本到文本转换转换器,它使得在任何文本到文本任务上微调转换器模型变得容易。任何 NLP 任务事件如果是分类任务,都可以被框定为输入文本到输出文本的问题。
在这篇博客中,我展示了如何在的任何数据集上调优这个模型。特别是,我演示了如何在汇总数据集上实现这一点。我在 CNN-每日邮报和维基百科数据集上亲自测试过。代码可以在我的 Github 这里公开获得。
T5-small 接受过 Wikihow 培训,能够写出令人惊叹的摘要。请参见下面的实际文本、实际摘要和预测摘要的片段。这个模型也可以在 HuggingFace 变形金刚模型中枢这里。该链接提供了一种在输入文本和 JSON 端点上测试模型的便捷方式。
WikiHow Text: Make sure you've got all the cables disconnected from the back of your console,
especially the power cord., You'll need the straight end to be about 2-3 inches long.You will need a
large size paper clip for this method because it will need to go in about 1 and a half inches to
push the disc out., It's located on the left side of the console, right behind the vents.The eject
hole on an Xbox One S is located at the second hole on the left from the right corner and the third
hole up from the bottom. It can be more difficult to spot, so it's best to have a good amount of
light available. Doing so will cause the disc to pop out a little bit., Carefully pull the disc the
rest of the way out with your fingers. It might be a good idea to use a cloth or soft fabric to
protect the disc from fingerprints and scratching.
Actual Summary: Unplug all cables from your Xbox One.Bend a paper clip into a straight line.Locate the orange circle.Insert the paper clip into the eject hole.Use your fingers to pull the disc out.
Predicted Summary: Gather the cables.Place the disc on your console.Section the eject hole on the left side of the console.Pull out the disc.Remove from the back of the console.
我经营一家机器学习咨询,深度学习分析。在深度学习分析公司,我们非常热衷于使用数据科学和机器学习来解决现实世界的问题。如果您正在为您的业务项目寻找 NLP 专业知识,请联系我们。原文全文发表在我们的网站这里。
T5 变压器型号
google research 发布的 T5 模型在现有研究的基础上增加了以下内容:
- 它创建了一个大规模通用爬网数据集的干净版本,称为巨大干净通用爬网(C4)。这个数据集是 s 比维基百科大两个数量级。
- 它在普通爬行上训练 T5
- 它建议将所有的自然语言处理任务作为输入文本重新组织为输出文本公式
- 它表明,使用预训练的 T5 和文本-文本公式化对不同任务(摘要、QnA、阅读理解)的微调产生了最先进的结果
- T5 团队还进行了系统研究,以了解预培训和微调的最佳实践。他们的论文详述了哪些参数对获得好的结果最重要。
来自 T5 论文的下图解释了这个输入文本到输出文本的问题公式。
T5 模型任务制定。图来自 T5 论文
谷歌的这篇博客也很好地解释了这篇论文。现在让我们深入研究代码吧!
T5 微调管道
我们将使用 T5 模型的 HuggingFace Transformers 实现来完成这项任务。非常感谢 Suraj 的这个棒极了的作品,我用它作为我代码的起点。
获取数据
为了简单地将这个管道扩展到任何 NLP 任务,我使用了 HuggingFace NLP 库来获取数据集。这使得加载许多支持数据集变得容易。HuggingFace NLP 库也支持许多指标。我已经为我的模型使用了它 rouge score 实现。
我的 Github 上有完整的代码。在这个演示中,我将展示如何处理 WikiHow 数据集。该代码可以灵活地扩展到任何摘要任务。
涉及的主要步骤是:
- 加载 Wikihow 数据。请注意,对于此数据集,需要将两个文件下载到本地数据文件夹
- NLP 库创建的 dataset 对象可用于查看示例
- 我们希望查看文本的平均长度,以决定输入是否可以标记为最大长度 512
对于 Wikihow 数据集,文本的平均长度是 660 个单词,摘要的平均长度是 49 个单词。下图显示了文本长度的分布
WikiHow 上的文本长度分布
WikiHow 文本通常是一个主题的 1-2 段说明性文本。下面分享一个例子
WikiHow Text: Each airline has a different seat design, but you should find a lever on the side of
your seat in many cases. Pull it up to bring the seat back up. If you can't find the lever, ask a
flight attendant for help., Most airlines still use seat belts that only go across your lap. Locate
the buckle on one side and the latching device on the other. Straighten out each side, if necessary.
Insert the buckle into the latching device. Make sure you hear a click. Pull the belt until it's
snug across the tops of your thighs., Do this even if the captain turns off the “Fasten Seat Belts”
sign. If you decide to recline, make sure the belt stays snug across your lap. If you're using a
blanket, place it between the belt and your body.
为数据创建 Pytorch 数据集类
接下来,我们定义一个 Pytorch 数据集类,它可以用于任何 NLP 数据集类型。对于文本到文本 T5,我们必须定义输入文本和目标文本的字段。这里,文章的“文本”是输入文本,“标题”是其摘要。
我使用了 512 个令牌的输入目标长度和 150 个输出摘要长度。wikihow 数据集类的输出是:
- source_ids:标记化的输入文本长度被截断/填充到最大长度 512
- source_mask:对应于输入令牌 id 的注意掩码
- target_ids:标记化的目标(摘要)文本长度被截断/填充到最大长度 150
- target_mask:对应于目标令牌 id 的注意掩码
我在 Github 上的笔记本有示例代码,您可以用它来玩数据集类,以检查输入是否被正确编码和解码。
定义 T5 调谐器
T5 调谐器是一个 pytorch lightning 类,它定义了数据加载器、模型前向传递、一步训练、一步验证以及时期结束时的验证。
我在这里添加了一些功能,以便更容易地使用它进行总结:
- 我已经使用了 NLP 库来导入 rouge_metric
- 我扩展了代码以在验证步骤生成预测,并使用这些预测来计算 rouge 指标
- 将 WANDB 添加为记录器
训练模型
我决定训练一个 T5 小模型。我对 train 和 val 都使用了 4 的批量大小,可以在大约 4 小时内在 GTX 1080Ti 上训练这个模型。该模型被训练了 2 个时期,并且 WANDB logger 在模型被训练时显示出 Rouge1 分数和 Val 损失的良好改善。
Rouge1 分数— Wikihow T5 小型 WandB 记录器
该模型的完整报告在这里分享。
测试模型
我已经把这个模型上传到 Huggingface Transformers 模型中心,并在这里测试。要在本地测试模型,可以使用 HuggingFace AutoModelWithLMHeadand 和 AutoTokenizer 特性来加载它。下面分享了这样做的示例脚本。
当前模型的主要缺点是输入文本长度被设置为最大 512 个标记。这对于许多总结问题来说可能是不够的。为了克服这个限制,我正在研究一个基于 Longformer 的摘要模型。我会很快分享我的博客!
结论
T5 是一款很棒的车型。有了足够的数据,针对任何 NLP 问题微调转换器变得很容易。在这篇博客中,我创建了一个代码外壳,可以适应任何摘要问题。
我希望您尝试一下代码,并训练自己的模型。请在下面的评论中分享你的经历。
在深度学习分析,我们非常热衷于使用机器学习来解决现实世界的问题。我们已经帮助许多企业部署了创新的基于人工智能的解决方案。如果您看到合作的机会,请通过我们的网站这里联系我们。
用 Python 实现高精度文本分类
如何微调 Huggingface 模型以获得 99%准确率的文本分类器。
布拉格的图书隧道,图片来自pixabay.com
在撰写本文时,NLP 和 NLU 任务的最新结果是通过 Transformer 模型获得的。随着模型变得更深更大,有一种性能提高的趋势, GPT 3 浮现在脑海中。从头开始训练这种模型的小版本需要大量的时间,即使使用 GPU 也是如此。这个问题可以通过预训练来解决,当使用高性能集群在大型文本语料库上训练模型时。稍后,它可以在更短的时间内针对特定任务进行微调。在微调阶段,可以为特定任务向模型添加附加层,这些任务可以不同于模型最初被训练的那些任务。这项技术与迁移学习有关,这是一个应用于 NLP 以外的机器学习领域的概念(快速介绍见此处和此处)。
在这篇文章中,我想分享我微调伯特和罗伯塔的经历,这两个角色可以通过拥抱脸从变形金刚图书馆获得,用于一个文档分类任务。两种型号共享一个变压器架构,该架构至少由两个不同的模块组成——编码器和解码器。编码器和解码器都由基于注意力机制的多层组成。编码器将输入的令牌序列处理成浮点数向量,这是一种隐藏状态,可以被解码器获取。隐藏状态包含了输入序列的信息内容。这使得能够用浮点数的单个密集向量来表示整个令牌序列。具有相似含义的两个文本或文档由紧密对齐的向量表示。使用选择的度量来比较向量,例如余弦相似度,使得能够量化原始文本片段的相似度。
螺栓和螺母
在研究这个话题时,我在 Kaggle 上找到了一篇关于微调 BERT 以分类假新闻数据集的文章。按原样运行文章中的代码得到的 F1 分数远低于声称的 96.99%。经过一个周末的阅读和添加一些东西,我已经设法在一个测试集上为罗伯塔模型挤出了 99.05%的 F1 分数,该测试集带有两个额外的线性层(代码)。
首先,让我们简单看一下假新闻数据集。由正文超过 5 个字的 6299 条,假的 3128 条,真的 3171 条组成。下图显示了文本长度分布的直方图,以 5000 个标记裁剪。数据集中存在令牌计数高达 20000 的文档。
批量大小和序列长度权衡。BERT 和 RoBERTa 在其基本配置中都被限制为 512 个令牌序列。GPU 内存限制会进一步减少最大序列长度。用批量换取序列长度是可能的。在“语言模型是一次性学习者”的论文中,作者提到了在训练后期更大批量的好处。随着微调在预训练结束时进行,较高的批量会产生更好的结果,并在一定程度上减少过度拟合。
训练批次中的序列可以有不同的长度。这需要将填充标记附加到每个序列,以使它们具有相同的长度。它可以使用专用的标记器来完成,由拥抱脸和相应的模型慷慨地提供:
Torchtext 库提供了几个易于使用的瑞士军刀迭代器。除此之外,它们能够将相似长度的序列分组并填充它们,将数据集分成训练集、验证集和测试集,必要时进行分层,在每个时期后进行洗牌。
**注意力面具进行批量训练。**填充的训练批次被传递给 RoBERTa,RoBERTa 输出一批隐藏状态向量,每个训练批次序列一个。填充索引不代表任何有用的信息。批处理中每个序列的结尾由一个特殊的字符串结束标记表示。事实上,一批大小为 1 的根本不需要任何填充。因此,填充索引应该从注意力权重计算中排除。这是借助于注意力屏蔽张量来实现的:
对于填充标记,掩码张量的值为 0(假),对于所有其他标记,掩码张量的值为 1(真)。它通过逐元素乘法应用于关注层的键输入,这将填充标记的贡献减少到 0。如果批量进行验证和测试,也应在验证和测试期间应用注意屏蔽。
预微调和学习率。训练期间RoBERTa 的输出是一批隐藏状态,传递给分类器层:
当上述模型被初始化时,RoBERTa 被分配预先训练的参数。因此,微调应以较小的学习速率进行,大约为 1e-5 。然而,分类器层被赋予其参数的随机未训练值。为此,我使用冻结的 RoBERTa 参数和更高的学习率 1e-4 运行了几个训练时期,同时只调整了分类器层参数。接下来,用同时更新的所有参数训练整个模型。
在训练的最后一步,一个线性学习率调度器,在每个训练步骤更新优化器学习率,证明是非常有益的。在前两个时期,优化器正在预热—学习率增加到其最大值 2e-6 ,这使得模型能够探索局部参数空间。在随后的时代,学习率逐渐降低到零。
成绩汇总
Huggingface 库提供了现成的序列分类器。这些模型有一个以“ForSequenceClassification”结尾的名字,这是不言而喻的。它和上面的模型是一样的,但是有一个单一的线性层,前面有一个辍学。我用 BERT 和 RoBERTa 以及两个现成的模型“BertForSequenceClassification”和“RobertaForSequenceClassification”来训练我的模型。对于所有 BERT 模型,都使用了套管配置。下表显示了一组测试结果的简要总结。
训练集包含 70%的数据(4410 个项目),10%的数据(629 个项目)在验证集中,20%的数据(1260 个项目)在测试集中。看起来罗伯塔的表现只好了一点点。然而,“ROBERTAClassifier”的错误率是测试样本的 1%,比“BERTClassifier”低 3 倍,后者的错误率几乎是 3%。
总之,通过微调最先进的模型,可以实现非常好的文本分类准确度,在本例中为 99%。对于后者,大声喊出来的是拥抱脸团队!
进一步改进
事实上,所有型号都只能读取前 256 个令牌。套用一句古老的谚语,通过包括标题在内的前几百个标记来判断一条新闻可能不太准确。改善结果的一个显而易见的方法是让模型多读一些文本。克服文本大小限制的一种方法是将文本分割成长度易于管理的块。用 RoBERTa 对几个组块进行编码会产生一系列隐藏状态,这些隐藏状态包含了比单个第一组块更多的文本信息。为了将隐藏状态组合成单一向量,可以使用一系列技术,例如简单的平均或 RNN 单元。得到的聚合向量可以传递给后续层。这种模型有可能在新闻是真是假的问题上做出更明智的决定。
微调用于 FARM 文本分类的 BERT
使用最先进的 NLP 模型进行简单快速的迁移学习
蒂莫西·埃伯利在 Unsplash 上的照片
去年秋天,当我在硕士论文的背景下努力微调预先训练的多语言 BERT 模型以进行论证挖掘(检测文本中的论证结构)时,我偶然发现了由 Deepset.ai 开发的开源框架FARM(Fframework forAadaptingRpresentationMmodels】。他们不仅提供了一个德国 BERT 模型,而且还提供了一个易于实现的框架,具有迁移学习的广泛特征。我不能说它拯救了我的论文,但至少拯救了我的神经和头发😅。
在这篇文章中,我提供了一个使用 D. Greene 和 P. Cunningham [1]在原始 BBC 新闻文章数据集上的框架对新闻文章体裁进行分类的指南。准备资料的过程可以在我上一篇文章中找到。
首先,我将简要介绍迁移学习和 BERT。然后,我会给你提供指导,让你成为一个真正的农民👩🌾👨🌾。
BERT 与迁移学习
一年前,Devlin、Chang、Lee 和 Toutanova 发表了 BERT(变压器的双向编码器表示法)[2]。这种创新的新模型在 11 个自然语言处理任务中取得了新的最先进的结果,如问题回答(SQuAD)或命名实体识别(NER)。
它结合了技术创新,如语言建模变压器的双向训练与各种不同任务的迁移学习能力。如果你有兴趣了解更多关于这个模型的信息,我强烈建议你阅读最初的论文,因为我不会更详细地描述它。
将从一个问题学到的知识应用到一个新的不同的问题上代表了迁移学习的思想。如果我们仔细想想,人类的学习在很大程度上是基于这种学习方式。由于迁移学习,学习 Java 对我来说很容易,因为当我开始学习时,我已经理解了编程概念和 Python 语法。
然而,为了真正成功地解决新问题,对未知任务进行专门的微调是必要的。因此,我们将在下面的章节中发现如何使用预先训练好的 BERT 模型,并对其进行微调,以便根据新闻文章的文本对其体裁进行分类。
成为农民👩🌾👨🌾
在这个简短的主题介绍之后,让我们为这个领域做准备。
首先,我们可以看看农场的可能性。该框架不仅支持使用英语的 BERT 进行文本分类,还支持其他几个下游任务,包括多种语言的一些最新模型。
可用模型和支持的农场任务— 来源
所以,现在我们已经准备好去现场了。启动你首选的 IDE(我会在 Google Colab 中描述如何使用),深呼吸,我们走吧。
设置
在 Colab 中键入并运行第一行之前,应该在笔记本设置(编辑>笔记本设置)中选择 GPU 作为硬件加速器。这将大大加快训练速度。
为了在 Google Colab 中使用 FARM,你必须安装它。有两种不同的方法可以做到这一点:
如果你想使用一个稳定的版本,那么在 Github 库的 release 标签下寻找最新的版本。目前,最新的版本是 0.4.3,我们正在使用以下命令安装 Google Colab:
!pip install farm==0.4.3
如果您想使用他们的前沿(尚未发布)特性,那么使用下面的命令安装它。但是请注意,这种方法不一定会安装一个稳定的版本,事情可能不会像预期的那样顺利。
!git clone https://github.com/deepset-ai/FARM.git
!pip install -r FARM/requirements.txt
!pip install FARM/
在将 FARM 安装到我们的环境中之后,我们还需要加载数据来训练我们的模型。通过克隆我的 GitHub 库,我们可以访问来自 Colab 的数据。
!git clone https://github.com/guggio/bbc_news
如果我们在 Colab UI 中打开左侧面板,我们现在可以看到 bbc_news 文件夹被添加到我们的文件中。为了进行微调,我们将使用 bbc_news 目录下 generated_data 文件夹中的训练和测试数据。
下一步,我们想要导入训练步骤所需的所有类和函数。
如果我们对实验更详细的分析感兴趣,我们可以在 FARM 的公共 MLflow 服务器上跟踪培训。
因此,我们几乎准备好进入真正的编码部分。但是在开始之前,让我们初始化一些我们在这个过程中需要的环境变量。
我们设置一个种子值,使运行可重复(每次运行都进行相同的洗牌),并获取正确的设备来加速训练过程。此外,我们确定训练时期的数量、批量大小和在开发集上评估我们的模型的频率。
数据处理
我记得我拼命地试图预处理我的数据以进行论证挖掘(按标记分类,类似于 NER),但最终失败了,因为 BERT 的单词块标记化遵循了 HuggingFace 的传统方法。
幸运的是,由于 FARM 的数据处理结构,FARM 的预处理任务要方便得多。此外,这种基于块的结构使得该过程高度可定制。
基于块的农场数据处理— 来源
为了将输入(文件或请求)转换成 PyTorch 数据集,我们使用了处理器。
为了完成它的工作,处理器需要一个标记器,我们可以简单地根据我们想要的语言模型来加载它。因为我们正在处理包含大写和小写单词的英语文本,所以我们将使用基本的大小写 BERT 模型并将 do_lower_case 设置为 false。
初始化标记器后,我们创建处理器来处理数据。如前所述,我们可以根据需要定制数据处理流程的模块。
因此,我们可以简单地将所需的定制作为参数输入到处理器构造器中。对于我们的文本分类任务,我们使用 TextClassificationProcessor 类。对于不同的任务,我们显然会切换到相应的处理器类别。
一旦创建了处理器,我们就可以加载数据仓库了。
建模和培训
上面的概述显示了该框架在适用的语言模型和支持的任务方面的通用性。这种灵活性需要一种适应性模型,该模型具有易于更换的组件,以满足相应任务和模型的要求。
农场的适应模式— 来源
自适应模型由我们想要微调的预训练语言模型和一个或多个预测头组成。预测头是放在模型上的最后一层,用于将模型的矢量表示转换为实际预测。
关于自适应模型的结构已经说得够多了,让我们来定义我们的模型并初始化优化器。
在我们开始训练我们的模型之前,还需要一个步骤。我们必须把一切都反馈给训练者,训练者管理训练过程,并在开发集上以定义的频率评估模型。
现在,是按下按钮的时候了。让培训开始:
trainer.train()
这将启动我们数据集的训练过程,需要 3-4 分钟。那么是时候收获我们播种的东西了🌽
培训结果
在两个时代之后,该模型在测试集上表现得非常好,总体宏观平均 F1 分数为 0.97。请注意,这些结果是在没有任何超参数调整的情况下获得的。
为了最大化我们模型的性能,我们可以使用不同的设置重新运行训练,例如不同的时期数、不同的批量大小、不同的退出率等。超参数优化的另一个选择是运行基于 json 配置文件的实验,我可能会在以后的文章中描述。
如果我们在开始时初始化 ML-logging,我们现在可以在 MLflow 服务器上访问我们实验的进一步信息,例如训练损失的发展。
培训损失
保存和运行推理
在训练了我们的模型之后,我们肯定也想在不同的文本上尝试它。不幸的是,我们不能直接在训练好的模型上运行推理,因为自适应模型类不提供这样的功能。因此,我们必须保存它,以便用推理器加载它。
我们也可以从 Colab 中提取模型,并用下面的命令下载它。转到 Colab UI 的左侧面板,选择 zip 文件并下载它。
!zip -r saved_models/model.zip saved_models/bert-english-news-article
为了对样本文本进行推理,我们需要在字典列表中对它们进行格式化,其中“text”是键,实际的文章文本表示相应的值。
为了方便推理步骤,我准备了助手函数,并在我的库中添加了两篇文章文本。一个额外的助手从结果中选择预测并返回一个数据帧。
让我们把准备好的文章分类!
推理的结果
结论
如果你做到了这一步,你会有一个美好的收获!🌽
如本文所述,FARM 迁移学习是一个简单直接的过程,提供了许多定制的可能性。因此,你可以考虑用农场来解决下一个 NLP 问题。
我希望我的教程对你有帮助和价值!如果我的指南有什么不清楚的地方,请随时问我。你可以在这里查看我的源代码。
非常感谢你的阅读和快乐编码!
参考
1d .格林和 p .坎宁安。核心文档聚类中对角优势问题的实际解决方案。ICML 2006。
[2] Devlin,j .,Chang,m-w .,Lee,k .,& Toutanova,K. (2019 年)。BERT:用于语言理解的深度双向转换器的预训练。谷歌人工智能语言。https://arxiv.org/pdf/1810.04805.pdf
使用 Pytorch 微调用于文本生成的 GPT2
使用 Huggingface 库提供的 GPT2 生成任何故事
介绍
在过去的几年里,NLP 的世界特别繁荣。这主要得益于 NLP 在现代十年最重要的突破之一——变形金刚 。如果你没有看过我之前关于 BERT 进行文本分类 的文章,那就去看看吧!我们今天要说的另一款热门变压器是 GPT2 。GPT2 由 OpenAI 开发,是一个基于 transformer 的大规模语言模型,在一个大型文本语料库上进行预训练:800 万个高质量网页。它只使用预先训练的知识,而没有对它们进行明确的训练,从而在多种语言任务上产生竞争性的表现。GPT2 对于语言生成任务非常有用,因为它是一个自回归语言模型。
在今天的文章中,我们将深入探讨如何实现另一个流行的转换器 GPT2,以编写有趣和有创意的故事!具体来说,我们将使用 CMU 图书摘要数据集测试 GPT2 撰写有创意的图书摘要的能力。我们将使用 Huggingface 库来构建我们的模型并生成文本。
本文的完整代码库可以在这里查看。
步骤 1:准备数据集
在构建模型之前,我们需要先下载并预处理数据集。
我们使用的是 CMU 图书摘要数据集,其中包含从维基百科中提取的 16,559 本图书,以及元数据,包括书名、作者、出版日期、流派和情节摘要。点击下载数据集。以下是数据集的外观:
作者图片
对于数据预处理,我们首先将整个数据集分成训练、验证和测试数据集,训练有效测试比率为 70–20–10。我们在每个摘要的开头添加了一个 bos 令牌,在每个摘要的结尾添加了一个 eos 令牌,以供以后培训使用。我们最终将摘要保存到。txt 文件,获取 train.txt,valid.txt,test.txt。
你可以在这里获得预处理笔记本。
步骤 2:下载库
为了构建和训练 GPT2,我们需要安装 Huggingface 库,以及它的存储库。
安装 Huggingface 库:
pip install transformers
克隆拥抱脸回购:
git clone github.com/huggingface/transformers
如果您想在训练期间看到模型和超参数的可视化效果,也可以选择安装 tensorboard 或 wandb:
pip install tensorboardpip install wandb; wandb login
第三步:微调 GPT2
在训练之前,我们应该按照之前在数据集中定义的那样设置 bos 令牌和 eos 令牌。
我们还应该设置 pad 令牌,因为我们将使用 LineByLineDataset ,它将把数据集中的每一行都视为不同的示例。在transformers/example/language-modeling/run-language-modeling . py中,我们应该在训练之前为模型追加以下代码:
special_tokens_dict = {'bos_token': '<BOS>', 'eos_token': '<EOS>', 'pad_token': '<PAD>'}num_added_toks = tokenizer.add_special_tokens(special_tokens_dict)model.resize_token_embeddings(len(tokenizer))
运行这段代码后,特殊的标记将被添加到标记器中,模型将调整其嵌入的大小,以适应修改后的标记器。
对于训练,我们首先定义一些参数,然后运行语言建模脚本:
cd transformers/example/language-modelingN=gpu_numOUTPUT_DIR=/path/to/modelTRAIN_FILE=/path/to/dataset/train.txtVALID_FILE=/path/to/dataset/valid.txtCUDA_VISIBLE_DEVICES=$N python run_language_modeling.py \--output_dir=$OUTPUT_DIR \--model_type=gpt2 \--model_name_or_path=gpt2 \--do_train \--train_data_file=$TRAIN_FILE \--do_eval \--eval_data_file=$VALID_FILE \--per_device_train_batch_size=2 \--per_device_eval_batch_size=2 \--line_by_line \--evaluate_during_training \--learning_rate 5e-5 \--num_train_epochs=5
由于 GPU 的限制,我们设置 per_device_train_batch_size=2,per_device_eval_batch_size=2。请随意使用适合您的 GPU 的批量大小。我们使用 line_by_line,它告诉我们的模型将数据集中的每一行都视为一个单独的示例,如前所述。Evaluate_during_training 在每个logging_steps
之后对评估数据集进行评估,默认为 500。
如果您想从最后一个检查点继续训练,您可以运行:
CUDA_VISIBLE_DEVICES=$N python run_language_modeling.py \--output_dir=$OUTPUT_DIR \--model_type=gpt2 \--model_name_or_path=$OUTPUT_DIR \--do_train \--train_data_file=$TRAIN_FILE \--do_eval \--eval_data_file=$VALID_FILE \--per_device_train_batch_size=2 \--per_device_eval_batch_size=2 \--line_by_line \--evaluate_during_training \--learning_rate 5e-5 \--num_train_epochs=5 \--overwrite_output_dir
(可选)步骤 4:评估测试数据集的困惑
这一步是可选的,取决于你是否想评估你训练过的 GPT2 的表现。您可以通过在测试数据集上评估困惑来做到这一点。
TEST_FILE=/path/to/dataset/test.txtCUDA_VISIBLE_DEVICES=$N python run_language_modeling.py \--output_dir=$OUTPUT_DIR \--model_type=gpt2 \--model_name_or_path=$OUTPUT_DIR \--do_eval \--eval_data_file=$TEST_FILE \--per_device_eval_batch_size=2 \--line_by_line
这里,在我的例子中,在训练 5 个时期后,我们获得了 2.46 的损失和 11.70 的困惑度:
作者图片
步骤 5:生成文本
在使用我们训练好的模型生成文本之前,我们首先通过在transformers/examples/text-generation/run _ generation . py中设置add_special_tokens=True
来启用提示中的特殊标记:
encoded_prompt = tokenizer.encode(prompt_text, add_special_tokens=True, return_tensors=”pt”)
然后,我们准备生成一些文本!开始生成方式:
cd transformers/examples/text-generationK=k_for_top-k_sampling_decoderCUDA_VISIBLE_DEVICES=$N python run_generation.py \--model_type gpt2 \--model_name_or_path $OUTPUT_DIR \--length 300 \--prompt "<BOS>" \--stop_token "<EOS>" \--k $K \--num_return_sequences 5
我们输入提示“”作为输入,它代表每个例子的开始,一旦生成了“”标记,就停止模型的生成。这样,我们的 GPT2 将学习从头到尾生成一个完整的摘要示例,利用它在培训期间从 bos 令牌和 eos 令牌中学到的知识。此外,我们正在使用 top-k 采样解码器,该解码器已被证明在生成非竞争性和更好的文本方面非常有效。k=50 是一个很好的开始值。Huggingface 还支持其他解码方法,包括贪婪搜索、波束搜索和 top-p 采样解码器。有关更多信息,请查看model.generate
的文档串。
下面是几个 k=50 的生成文本的例子。
主角是英国人威廉·拉克,他被英国政府派往北极执行任务,开始了一次冒险之旅。这部小说讲述了他的朋友和家人如何被卖到挪威小镇肖克当奴隶的故事…
一个新的世界正在觉醒,沃尔塔星球的人类必须齐心协力拯救它免于毁灭。新地球现在居住着三个物种。第一个是年龄稍大的人类,第二个是沃尔塔人,第三个是有着深蓝色眼睛的人类…
这部小说开始于 2143 年,一群“地牢”或女巫决定通过消耗死者的灵魂来打破阻止死者力量的咒语。他们用尸体来帮助垂死的人,也用尸体来复活死者…
你可以在这里看到更多生成的例子。
结论
在本文中,我们展示了如何实现最流行的 transformer 模型之一 GPT2 来创建有趣的文本。GPT2 的大规模预训练数据集和架构允许它产生连贯和流畅的写作片段。虽然 GPT2 的文本仍然可以与人类书写的文本区分开来,但这证明了机器的创造力只是从现在开始上升。想了解更多信息,你可以看看 GPT2 上的官方论文或者 OpenAI 的博客。
本文只展示了如何生成由人工智能决定的文本。如果您想知道是否有可能控制正在生成的文本(这是可能的!),看看我写的下面这篇文章😊。
控制机器生成文本的样式和内容的实际操作方法
towardsdatascience.com](/controlling-text-generation-from-language-models-6334935e80cf)
参考
[1] A .瓦斯瓦尼,n .沙泽尔,n .帕尔马等。,注意力是你需要的全部 (2017),第 31 届神经信息处理系统会议
[2] A .、j .吴、r .柴尔德等。,语言模型是无监督的多任务学习器 (2019),OpenAI
在 Colab GPU 上微调 GPT2 免费!
利用 Google Colab 的 GPU 来微调预训练的 GPT2
现在的模特都很大,我们大多数人都没有资源从头开始训练她们。幸运的是, HuggingFace 已经慷慨地在 PyTorch 中提供了预训练模型,并且 Google Colab 允许使用他们的 GPU(在固定时间内)。否则,即使在没有 NVIDIA GPU 的情况下,在我的本地计算机上微调数据集也会花费大量时间。虽然这里的教程是针对 GPT2 的,但这可以针对 HuggingFace 给出的任何预训练模型进行,也可以针对任何尺寸。
设置 Colab 使用 GPU…免费
转到 Google Colab 并创建一个新笔记本。它应该看起来像这样。
点击Runtime
> Change runtime type
设置使用 GPU
然后点击Save
。
安装依赖项
我们通常会在 Bash 中运行pip3 install transformers
,但是因为这是在 Colab 中,所以我们必须用!
来运行它
!pip3 install transformers
获取 WikiText 数据
你可以在这里阅读更多关于 WikiText 数据的信息。总的来说,有 WikiText-2 和 WikiText-103。我们将使用 WikiText-2,因为它更小,而且我们在 GPU 上运行的时间以及在 Colab 中可以加载到内存中的数据量都有限制。要下载并运行,请在单元格中运行
%%bash
wget [https://s3.amazonaws.com/research.metamind.io/wikitext/wikitext-2-raw-v1.zip](https://s3.amazonaws.com/research.metamind.io/wikitext/wikitext-2-raw-v1.zip)
unzip wikitext-2-raw-v1.zip
微调 GPT2
HuggingFace 实际上提供了一个脚本来帮助微调模型这里。我们可以通过运行以下命令来下载脚本
!wget https://raw.githubusercontent.com/huggingface/transformers/master/examples/language-modeling/run_language_modeling.py
现在我们准备微调。
脚本有很多参数,你可以通过阅读手册来理解。我只是想复习一下基础训练中重要的内容。
output_dir
是模型输出的位置model_type
就是你要用什么型号。在我们的例子中,它是gpt2
。如果你有更多的内存和时间,你可以选择更大的gpt2
尺寸,这些尺寸列在抱脸预训练型号列表中。model_name_or_path
是通往模型的道路。如果您想从头开始培训,可以留空。在我们的例子中,它也是gpt2
do_train
告诉它要训练train_data_file
指向培训文件do_eval
告诉它事后评估。不总是必需的,但最好有eval_data_file
指向评估文件
一些你可能关心的额外的,但是你也可以跳过这个。
save_steps
是何时保存检查点。如果您的内存有限,您可以将此设置为-1
,这样它会跳过保存直到最后per_gpu_train_batch_size
是 GPU 的批量大小。如果你的 GPU 有足够的内存,你可以增加这个。为了安全起见,你可以从 1 开始,如果你还有内存的话,就增加它num_train_epochs
是要训练的历元数。由于我们正在微调,我将把它设置为2
- 如果
output_dir
中已经有内容,就使用overwrite_output_dir
,您可以覆盖现有的模型
总而言之,要训练,在牢房里跑这个
%%bash
export TRAIN_FILE=wikitext-2-raw/wiki.train.raw
export TEST_FILE=wikitext-2-raw/wiki.test.raw
export MODEL_NAME=gpt2
export OUTPUT_DIR=outputpython run_language_modeling.py
--output_dir=$OUTPUT_DIR \
--model_type=$MODEL_NAME \
--model_name_or_path=$MODEL_NAME \
--do_train \
--train_data_file=$TRAIN_FILE \
--do_eval \
--eval_data_file=$TEST_FILE \
--per_gpu_train_batch_size=1 \
--save_steps=-1 \
--num_train_epochs=2
请注意,如果您想要微调您刚刚训练的模型,您可以将MODEL_NAME=gpt2
更改为MODEL_NAME=output/
,这样它将加载我们刚刚训练的模型
花很长时间跑
当您运行这个程序时,如果花了一些时间没有任何输出,您可以将鼠标悬停在右上角的 RAM/Disk 上,看看发生了什么。
Colab GPU 的缺点是它是在 Colab 用户之间共享的。这意味着可能不会立即执行,因为另一个用户正在使用它。当这种情况发生时,它会说
Waiting for 'Python 3 Google Compute Engine backend (GPU(' to finish its current execution.
除了静观其变,实在没什么可做的。
结果
运行完模型后,您可以检查它是否存在于输出目录中。
要使用它,您可以运行类似
其中= Toronto Raptors =
相当于把Toronto Raptors
描述为文章标题。
我得到的结果(和你的会有所不同)是
= Toronto Raptors = Toronto's first @-@ round draft pick in 2006 was selected by the Toronto Raptors with the seventh overall pick. He played in all 82 games, averaging 18 points per game and 6 assists. The Raptors won their third straight NBA championship in 2007, and won the 2009 NBA All @-@ Star Game. He played in a record 16 games for Toronto, averaging 19 points on 5 @.@ 6 rebounds and 6 assists in a season that saw Toronto win the Eastern Conference finals. He also played in the 2008 All @-@ Star Game and was named to the All @-@ Star Game MVP for the first time. He also was named to the All @-@ Star Game's all @-@ time career scoring list, and was the first player to accomplish the feat. He finished the season with an assist and an assist in eight games, and was the first player in NBA history to score in double figures. He was named to the All @-@ Star Game's All @-@ time scoring list in 2011, and was the first player to do this in consecutive seasons. = = Draft = = Toronto selected Jordan Matthews with the seventh overall pick in
在我的例子中,我只生成了前 250 个单词,这就是为什么它会被突然删除的原因。如果你想的话,你可以扩展它。请注意,对Toronto Raptors
的描述完全是假的,因为乔丹·马修斯从未为猛龙队效力。文本一致性也可以更好,这可以通过使用更多的 epochs 进行调整,或者简单地使用更大的模型。然而,这需要更多的内存,所以要小心。
压缩/压缩模型
为了让我们保存这个模型,我们应该压缩它并保存在某个地方。这可以很容易地完成
! tar -czf gpt2-tuned.tar.gz output/
这会创建一个名为gpt2-tuned.tar.gz
的文件
保存到 Google Drive
要将它从 Colab 保存到您的 Google Drive,首先您必须有一个 Google 帐户/Gmail 帐户。在你的牢房里,你可以跑
from google.colab import drive
drive.mount('/content/drive')
不需要安装任何额外的东西,因为google.colab
图书馆附带使用谷歌 Colab。当您运行上述代码时,您应该会看到类似这样的内容
你必须点击链接,登录并允许你的 Google Drive 访问你的 Colab。最后,你会看到类似这样的东西
复制并粘贴到你的笔记本上。
现在,您可以通过运行以下命令将输出模型复制到您的 Google Drive 中
!cp gpt2-tuned.tar.gz /content/drive/My\ Drive/
结论
瞧啊。您已经成功地在 GPU 上调优了一个预训练模型,并将其保存在您的 Google Drive 中。而且你完全免费。
如果你对我们的工作有任何疑问或改进,请在评论中告诉我。
奖励:Colab 笔记本
您可以运行这个 Colab 笔记本来重现上面显示的所有内容
使用自定义数据集微调拥抱人脸模型
端到端示例,解释如何使用 TensorFlow 和 Keras 通过自定义数据集微调拥抱人脸模型。我展示了如何保存/加载训练好的模型,并使用标记化的输入执行预测函数。
作者:安德烈·巴拉诺夫斯基
有很多文章是关于用自己的数据集进行拥抱人脸微调的。很多文章都是用 PyTorch,有些是用 TensorFlow。我有一项任务是基于自定义投诉数据集实现情感分类。我决定用拥抱脸变形金刚,因为 LSTM 的效果不太好。尽管有大量可用的文章,但我花了大量的时间将所有的信息整合在一起,并用 TensorFlow 训练的拥抱脸实现了我自己的模型。似乎大多数(如果不是全部的话)文章在解释训练的时候就停止了。我认为分享一个完整的场景并解释如何保存/加载训练好的模型并执行推理会很有用。这篇文章基于 TensorFlow 的拥抱脸 API。
你应该从拥抱脸文档开始。有一个非常有用的部分— 使用定制数据集进行微调。为了了解如何使用自己的句子分类数据来微调拥抱人脸模型,我建议学习这一部分的代码— 与 IMDb 评论的序列分类。拥抱脸文档为 PyTorch 和 TensorFlow 都提供了例子,非常方便。
我正在使用tfdistilbertfsequenceclassification类来运行句子分类。关于distil Bert—distil Bert 是由蒸馏 Bert base 训练出来的小型快速廉价轻便的变压器模型。根据 GLUE 语言理解基准测试,它比 bert-base-uncased 少 40%的参数,运行速度快 60%,同时保留了超过 95%的 bert 性能。
from transformers import DistilBertTokenizerFast
from transformers import TFDistilBertForSequenceClassificationimport tensorflow as tf
- 导入并准备数据
一个例子是基于使用报纸标题的讽刺分类。数据由劳伦斯·莫罗尼准备,作为他的 Coursera 培训的一部分(源代码可在 GitHub 上获得)。我直接从劳伦斯的博客中获取数据:
!wget --no-check-certificate \
[https://storage.googleapis.com/laurencemoroney-blog.appspot.com/sarcasm.json](https://storage.googleapis.com/laurencemoroney-blog.appspot.com/sarcasm.json) \
-O /tmp/sarcasm.json
然后是数据处理步骤,读取数据,将其分成训练/验证步骤,并提取一组标签:
training_size = 20000with open("/tmp/sarcasm.json", 'r') as f:
datastore = json.load(f)sentences = []
labels = []
urls = []
for item in datastore:
sentences.append(item['headline'])
labels.append(item['is_sarcastic'])training_sentences = sentences[0:training_size]
validation_sentences = sentences[training_size:]
training_labels = labels[0:training_size]
validation_labels = labels[training_size:]
有 20000 个条目用于训练,6709 个条目用于验证。
2.设置 BERT 并运行训练
接下来,我们将加载记号赋予器:
tokenizer = DistilBertTokenizerFast.from_pretrained('distilbert-base-uncased')
标记培训和验证句子:
train_encodings = tokenizer(training_sentences,
truncation=True,
padding=True)
val_encodings = tokenizer(validation_sentences,
truncation=True,
padding=True)
创建 TensorFlow 数据集,我们可以将其输入 TensorFlow fit 函数进行训练。这里我们用标签映射句子,不需要单独将标签传入 fit 函数:
train_dataset = tf.data.Dataset.from_tensor_slices((
dict(train_encodings),
training_labels
))val_dataset = tf.data.Dataset.from_tensor_slices((
dict(val_encodings),
validation_labels
))
我们需要一个预先训练好的拥抱脸模型,我们将根据我们的数据对其进行微调:
# We classify two labels in this example. In case of multiclass
# classification, adjust num_labels valuemodel = TFDistilBertForSequenceClassification.from_pretrained('distilbert-base-uncased', num_labels=2)
通过调用 TensorFlow fit 函数,用我们的数据微调模型。它来自tfdistillbertforsequenceclassification模型。您可以使用参数进行游戏和试验,但是所选择的选项已经产生了相当好的结果:
optimizer = tf.keras.optimizers.Adam(learning_rate=5e-5)
model.compile(optimizer=optimizer, loss=model.compute_loss, metrics=['accuracy'])
model.fit(train_dataset.shuffle(100).batch(16),
epochs=3,
batch_size=16,
validation_data=val_dataset.shuffle(100).batch(16))
在 3 个时期中,它达到 0.0387 的损失和 0.9875 的精度,具有 0.3315 的验证损失和 0.9118 的验证精度。
用抱脸 save_pretrained 功能保存微调后的模型。使用 Keras 保存函数 model.save 进行保存确实有效,但是这种模型无法加载。这就是我使用 save_pretrained 的原因:
model.save_pretrained("/tmp/sentiment_custom_model")
保存模型是基本步骤,运行模型微调需要时间,您应该在训练完成时保存结果。另一个选择是——你可以在云 GPU 上运行 fine-running 并保存模型,在本地运行它以进行推理。
3.加载保存的模型并运行预测功能
我使用 TFDistilBertForSequenceClassification 类来加载保存的模型,方法是从 _pretrained 调用拥抱脸函数*(指向保存模型的文件夹)😗
loaded_model = TFDistilBertForSequenceClassification.from_pretrained("/tmp/sentiment_custom_model")
现在我们想运行预测功能,并使用微调模型对输入进行分类。为了能够执行推理,我们需要像对训练/验证数据那样对输入句子进行标记。为了能够读取推理概率,将 return_tensors=“tf” 标志传递给 tokenizer。然后使用保存的模型调用预测:
test_sentence = "With their homes in ashes, residents share harrowing tales of survival after massive wildfires kill 15"
test_sentence_sarcasm = "News anchor hits back at viewer who sent her snarky note about ‘showing too much cleavage’ during broadcast"# replace to test_sentence_sarcasm variable, if you want to test
# sarcasmpredict_input = tokenizer.encode(test_sentence,
truncation=True,
padding=True,
return_tensors="tf")tf_output = loaded_model.predict(predict_input)[0]
预测在拥抱人脸模型上运行的函数返回 logit(soft max 之前的分数)。我们需要应用 SoftMax 函数来获得结果概率:
tf_prediction = tf.nn.softmax(tf_output, axis=1).numpy()[0]
结论
这篇文章的目标是展示一个完整的场景,用自定义数据微调拥抱人脸模型——从数据处理、训练到模型保存/加载和推理执行。
源代码
- GitHub 回购
- 在 Colab 笔记本中自己运行
微调预训练模型 VGG-16
本文旨在展示如何在迁移学习中微调而不是使用预训练模型作为特征提取器,并在 RAVDESS 音频数据集上比较结果。
在我的上一篇文章中,我探索了使用预训练模型 VGG-16 作为在 RAVDESS 音频数据集上进行迁移学习的特征提取器。作为数据科学的新手,我通读了 Medium 上的文章,并看到了佩德罗·马塞利诺写的这篇便利的文章,其中他描述了迁移学习的过程,但最有见地的是人们可以微调他们选择的预训练模型的三种策略。佩德罗马塞利诺还提供了一个有用的大小相似性矩阵,以帮助确定使用哪种策略。在阅读了他的文章后,我开始意识到,与其使用预训练的模型作为特征提取器,我应该通过训练一些层并保持其他层冻结来微调模型,因为我的数据集很小(1440 个文件),并且与 VGG-16 模型数据集不相似。在这里,我将探索这种对 RAVDESS 音频数据集上的 VGG-16 预训练模型的微调,并确定其对模型准确性的影响。
在导入了必要的库、我们的训练/测试集,并预处理了数据(在这里描述为)之后,我们开始建模:
- 首先,导入 VGG16 并传递必要的参数:
from keras.applications import VGG16vgg_model = VGG16(weights='imagenet', include_top=False, input_shape=(224, 224, 3))
2.接下来,我们设置一些层冻结,我决定解冻最后一块,使他们的权重在每个时期得到更新
# Freeze four convolution blocks
for layer in vgg_model.layers[:15]:
layer.trainable = False# Make sure you have frozen the correct layers
for i, layer in enumerate(vgg_model.layers):
print(i, layer.name, layer.trainable)
作者图片
很好,所以我们将在预训练的 VGG-16 模型的最后四层训练我们的数据集。
3)我使用与我之前的 VGG-16 模型相同的模型架构作为特征提取器:
x = vgg_model.output
x = Flatten()(x) # Flatten dimensions to for use in FC layers
x = Dense(512, activation='relu')(x)
x = Dropout(0.5)(x) # Dropout layer to reduce overfitting
x = Dense(256, activation='relu')(x)
x = Dense(8, activation='softmax')(x) # Softmax for multiclass
transfer_model = Model(inputs=vgg_model.input, outputs=x)
4)我们来设置一些回调,比如 ReduceLROnPlateau 和 ModelCheckpoint。ReduceLROnPlateau 特别有助于微调我们的模型,因为正如佩德罗·马塞利诺所描述的,高学习率会增加丢失先前知识的风险,所以最好设置一个低学习率,有了 ReduceLROnPlateau,这可以帮助我们解决这个问题!ModelCheckpoint 总是有用的,因为它允许我们定义在哪里检查模型权重。
from keras.callbacks import ReduceLROnPlateaulr_reduce = ReduceLROnPlateau(monitor='val_accuracy', factor=0.6, patience=8, verbose=1, mode='max', min_lr=5e-5)checkpoint = ModelCheckpoint('vgg16_finetune.h15', monitor= 'val_accuracy', mode= 'max', save_best_only = True, verbose= 1)
4)接下来,我们编译并拟合我们的模型
from tensorflow.keras import layers, models, Model, optimizerslearning_rate= 5e-5transfer_model.compile(loss="categorical_crossentropy", optimizer=optimizers.Adam(lr=learning_rate), metrics=["accuracy"])history = transfer_model.fit(X_train, y_train, batch_size = 1, epochs=50, validation_data=(X_test,y_test), callbacks=[lr_reduce,checkpoint])
作者图片
作者图片
正如我们所看到的,该模型在很大程度上过度拟合了训练数据。经过 50 个时期后,我们的模型达到了 78% 的准确度,这比我们之前的分类器高 9%,在我们之前的分类器中,我们使用预训练的 VGG-16 模型作为特征提取器,但与我们预训练的 VGG-16 模型一样,使用图像增强作为特征提取器。
现在让我们尝试用图像增强来微调 VGG-16 模型,看看这是否会提高模型精度。使用我们之前模型的 transfer_model 变量中存储的相同的 VGG-16 模型对象,并解冻第五个卷积块,同时保持前四个块冻结。
for layer in vgg_model.layers[:15]:
layer.trainable = Falsex = vgg_model.output
x = Flatten()(x) # Flatten dimensions to for use in FC layers
x = Dense(512, activation='relu')(x)
x = Dropout(0.5)(x) # Dropout layer to reduce overfitting
x = Dense(256, activation='relu')(x)
x = Dense(8, activation='softmax')(x) # Softmax for multiclass
transfer_model = Model(inputs=vgg_model.input, outputs=x)for i, layer in enumerate(transfer_model.layers):
print(i, layer.name, layer.trainable)
作者图片
很好,现在第五个卷积块是可训练的,我们已经添加了自己的分类器。现在,我们将使用 Keras 的图像预处理模块的 ImageDataGenerator 来增强我们的图像,使其适合我们的训练集,编译模型,然后拟合模型。
#Augment images
train_datagen = ImageDataGenerator(zoom_range=0.2, rotation_range=30, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2)#Fit augmentation to training images
train_generator = train_datagen.flow(X_train,y_train,batch_size=1)#Compile model
transfer_model.compile(loss="categorical_crossentropy", optimizer='adam', metrics=["accuracy"])#Fit model
history = transfer_model.fit_generator(train_generator, validation_data=(X_test,y_test), epochs=100, shuffle=True, callbacks=[lr_reduce],verbose=1)
在 100 个时期之后,我们获得了 70% 的准确度分数,这比我们之前的模型降低了 8% ,并且作为特征提取器与我们的 VGG-16 模型表现相同(在此插入巨大的悲伤面孔)。
作者图片
可以清楚地看到,该模型过度拟合于训练数据,并且在大约 40 个时期之后,准确度似乎达到平稳状态。通过微调,与使用预训练模型作为特征提取器相比,我没有看到模型准确性有多大提高,这是我没有预料到的,因为与 VGG-16 模型相比,我使用的数据集不同且更小。
我已尝试调整 ImageDataGenerator 类的参数,但未能提高模型精度。如果有人对我的 VGG-19 模型有任何建议,请告诉我!感谢您的阅读:)
使用粒子群优化算法微调算法策略
如果组合的数量非常巨大,如何定义交易策略的次优参数。
今天我们讨论一种技术,它允许在有限的时间内搜索一组好的参数,我们也将考虑一种交易策略作为奖励。好好读!
目录
- 粒子群算法简介
- 交易策略算法
- 让我们编码吧!
- 实验
- 进一步改进
- 总结
粒子群优化算法简介
我们任务优化的主要目标是定义交易策略的次优参数,使目标函数最大化或最小化(通常使用最小化)。
目标函数可以很简单,比如我们应该最大化的算法的回报或者我们应该最小化的下降。在这个实验中,我们使用了一个更复杂的函数来尝试组合几个因子。我将在下一部分描述这个函数。
许多研究者在他们的著作中描述了 PSO 方法。我的任务是在一个地方收集该方法背后的主要算法方案和原理。此外,我还提供了链接来获取更多信息,并根据您的兴趣进行深入研究。
在计算科学中,粒子群优化(PSO)是一种计算方法,通过迭代地尝试改进关于给定质量度量的候选解决方案来优化问题。它通过拥有一群候选解(这里称为粒子)并根据粒子位置和速度的简单数学公式在搜索空间中移动这些粒子来解决问题。每个粒子的运动受其局部最佳已知位置的影响,但也被导向搜索空间中的最佳已知位置,当其他粒子找到更好的位置时,这些位置被更新。这有望将群体推向最佳解决方案。
PSO 是一种元启发式算法,因为它对被优化的问题几乎不做任何假设,并且可以搜索非常大的候选解空间。然而,像 PSO 这样的元启发式算法并不能保证找到最优解。此外,PSO 不使用被优化问题的梯度,这意味着 PSO 不要求优化问题是可微的,如梯度下降和拟牛顿法等经典优化方法所要求的。
听起来很棒,我们可以使用不可微的目标函数。这是一个非常方便的点,因为我们的功能通常是复杂的,由不同的部分组成。此外,该算法可以在没有假设的情况下在大空间中工作。该算法的方案如下所示
PSO 算法方案(来自篇
粒子群动画(来自维基百科)
你可以在这个来源文章中得到数学描述。该算法是一步一步尝试收敛的迭代,此图显示了该过程的动画。
一般接下来是独立于一门编程语言的具有基本操作的伪代码。
算法的伪代码(来自文章
由 Tarlan Ahadli 亲自动手实现的算法的好解释你可以在这里找到。另外,你可以看这个很棒的视频
如果你想深入了解这种方法的不同修改,我会推荐这个、这个和这个。
交易策略算法
策略的主要思想是试图抓住价格在反向突破趋势的时刻。之后我们开仓,努力赚钱。
趋势线基于两个支点。对于上升趋势线(绿色下线),下一个下降支点比前一个高**,下一个上升支点比前一个低**(红色上线)。****
一个向下的支点是棒线的最低价,最低价格在几个棒线的周围(每边至少 2 个棒线),一个向上的支点有一个反转逻辑。
当价格与下跌趋势线相交并且收盘高于这条线时,进入多头仓位。空头头寸的反转逻辑。
头寸的退出点是止盈(绿标)或止损(红标)。在这种情况下,固定水平用于 TP 和 SL。退出的替代事件以反转模式发生。
我们来编码吧!
在我们得到策略的描述之后,我们可以开始写代码了。使用回溯测试系统 Backtrader 。这是一个开源的功能丰富的框架,用于回溯测试和交易。是的,用 Python。
这个项目我分成 5 个文件,其中 4 个文件组成一个类,一个文件是包含主要实验并运行其他实验的脚本。
main.py 的代码包含了这里实验的逻辑。
该脚本包含三个重要部分:目标函数定义,在训练数据集上运行 PSO,以及在几个数据集(训练、测试、完整)上运行具有最佳参数的策略。由于使用了 PSO 库 pyswarm 。
在这个脚本中,我们创建了 BacktestTrendBreakerPL 类的对象。这个类负责数据读取、运行策略、计算指标和可视化结果。backtesttrendbreakerpl . py类的代码是
我们从主脚本传递过来的 output_settings 对象负责冗长性。
有趣的部分是stability _ of _ time series函数。该函数是研究的核心,直接影响优化过程。主要目标是利用传递的一组参数对交易策略进行质量评估。在数学上,它确定为累积对数收益的线性拟合的 R 平方。函数的符号取决于收益率。当该值接近 1.0 时,这意味着很好的结果,线随着小的下降而增长,当该值接近-1.0 时,结果不好。**
该脚本包含来自 DataFeedFormat.py 的 FinamHLOC 类,其中放置了源文件的数据结构代码。
下一件有趣的事情是来自TrendBreakerPL strategy . py的 TrendBreakerPL 类的一个对象。
该类的主要功能是职位管理。第一种是信号出现时进场。第二种是当价格达到止盈或止损,或反转形态出现时平仓。
附加功能是打印订单状态。
该脚本使用基于指示器数据的信号,该指示器数据在PivotPointLine indicator . py中的 PivotPointLine 类中定义。基本功能是计算枢轴点、趋势线和收盘价相交时的信号,并根据方向关闭上/下趋势线。
我认为这段代码不是最佳的,它在回溯测试的一般流程中花费了很多时间。
当我开发这个解决方案时,我遇到了我用于可视化和一些度量的最新版本的 Pyfolio 库的问题。我修改了几行代码,在这里是如何建议的。
另外,我像这样修改了 Pyfolio 的 plotting.py 中的几行代码,因为我需要一个 DataFrame 对象
实验
实验是在 Sberbank 数据上进行的。Sberbank 是俄罗斯、东欧和中欧最大的银行。
我从 Finam 下载了每小时的数据。期限从 2014 年 1 月 6 日至 2020 年 2 月 21 日。然后这个时期被划分为
列车期间(2014 年 1 月 6 日—2017 年 12 月 29 日);
测试周期(2018 年 1 月 3 日—2020 年 2 月 21 日)。
该组优化参数包括:
****pivot_window_len** - window length (the number of bars) by each side for determing the pivot point. Range is [2, 120];**history_bars_as_multiple_pwl** - history window length as a multiplication factor for pivot_window_len. Range is [10, 100];**fixed_tp** - value for the fixed level of take profit. Range is [0.01, 0.2];**fixed_sl_as_multiple_tp** - value for the fixed level of stop loss as a multiplication factor for fixed_tp. Range is [0.1, 1.5].*I used a multiplication factor instead of a simple value because it is easier to control and logically suitable in an optimization process. E.g., take profit and stop loss will be closer to each other.***
该实验在 40 次迭代时启动,其中的群体规模为 20 。群优化的其他参数是默认的( omega=0.5,phip=0.5,phig=0.5 )。列车数据的最佳参数集是:**
****pivot_window_len** = 9**history_bars_as_multiple_pwl** = 49**fixed_tp** = 0.06034064 (6.034%)**fixed_sl_as_multiple_tp** = 0.14778282\. It means stop loss is 0.892%As a result the objective function is -0.9712, then stability is 0.9712\. This is very good result because it's very close to 1.0.**
让我们看看我们实验的资本曲线
看起来相当不错。在训练阶段,我们有相同的回报水平,但下降幅度更低,曲线更稳定。测试期间的 algo 曲线展示了强劲的正动态,远好于基准(买入&持有)。最终曲线强于基准曲线。该算法的指标是
****Train:** Return: 106.45%
Stability: 0.971
Maximum Drawdown: 14.37%**Test:** Return: 63.17%
Stability: 0.7934
Maximum Drawdown: 11.58%**Full Period:** Return: 296.3%
Stability: 0.9738
Maximum Drawdown: 14.37%**
下一张图更有效地展示了下降周期
好处是在训练和测试期间水位下降稳定。甚至在测试期更低。让我们来看看前 5 名的提款
****Train:
** Net drawdown in % Peak date Valley date Recovery date Duration
1 14.365 2017-06-15 2017-12-27 NaT NaN
2 13.2355 2015-05-20 2015-07-07 2015-11-17 130
3 12.8431 2014-10-06 2014-12-02 2014-12-16 51
4 10.1103 2015-01-27 2015-02-12 2015-03-16 35
5 8.6618 2014-04-01 2014-04-22 2014-06-16 55**Test:**
Net drawdown in % Peak date Valley date Recovery date Duration
1 11.5807 2019-02-08 2019-03-18 2019-04-23 52
2 10.8415 2019-07-23 2019-12-02 NaT NaN
3 9.28844 2018-05-03 2018-06-25 2018-07-27 62
4 8.26704 2018-10-30 2018-12-03 2019-01-18 59
5 7.07621 2018-04-03 2018-04-09 2018-04-09 5**Full Period:
** Net drawdown in % Peak date Valley date Recovery date Duration
1 14.365 2017-06-15 2017-12-27 2018-03-21 200
2 13.2355 2015-05-20 2015-07-07 2015-11-17 130
3 12.8431 2014-10-06 2014-12-02 2014-12-16 51
4 12.1112 2019-02-08 2019-03-18 2019-05-03 60
5 11.5791 2019-07-23 2019-12-02 NaT NaN**
如我们所见,最高下降发生在列车运行期间。
下图展示了每小时的回报。总的来说,离群值的数量很少。
让我们来看看不同时间框架下的收益分位数图
的形状与相似,这是一个很好的迹象。接下来是月收益分布,均值远高于 0.0 ,负收益被压缩,没有异常值。****
实验的最终总结是,PSO 允许在有限的时间内获得相当好的质量,次优参数在具有相同盈利水平和下降水平的样本外数据集上工作。
进一步的改进
我建议研究接下来的时刻以获得更好的结果**😗*
- 尝试 PSO 参数。还有,你可以试着用另一种方法代替 PSO 进行优化,然后你们可以互相比较。
- 基于波动性的止盈止损。
- 跟踪止损,而不是止盈止损。
- 在一个职位上的有限时间。
- 任何模式触发平仓(例如,持仓时与趋势线相交)。
- 不同的时间范围(例如 15 分钟或 1 天)。
- 应用前进分析研究结果的稳定性和鲁棒性。
步行前进过程(来自篇
8.基于几个不相关的资产创建一个投资组合。
9.添加压力测试机制,用于策略稳健性验证。
10.添加更多的度量标准,如 alpha、beta 、 Sortino ratio 等等。
****这些改进需要大量的计算,你应该考虑:
- 重构pivotpointline indicator . py**,因为这是一个非常慢的实现,或者为特定的时间框架和参数准备指标的预计算缓存。**
- 在可能的地方尽量用 GPU。
- 在投资组合生成中使用多重处理模式。
- 检查 PSO 方法的其他实现。有些库可以在 GPU 和 CPU 多处理模式下进行计算。
摘要
- 描述了粒子群优化方法的快速介绍及其在算法交易中的应用。
- 形式化了交易策略的模式和逻辑。
- 演示了代码和描述,你可以在 GitHub 上获得。另外,注意 packages_env_list.txt 来安装正确版本的软件包。
- 训练和测试方法,并评估实验。
- 建议进一步改进。
在这种情况下,建议的方法为这种策略提供了良好的性能和适度的消耗**。**
所有的步骤和实验都是可重复的,你可以得到代码和数据。作为数据源,你可以免费试用这个由 Antoine Vulcain 提供的服务。
投资愉快!
最诚挚的问候,
谢尔盖
来自《走向数据科学》编辑的提示: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们并不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
微调 XGBoost 模型
让你的模型更好的基本要素
调整模型是增强模型性能的方法。让我们看一个例子,在这个例子中,根据 RMSE 分数,对未调优的 XGBoost 模型和调优的 XGBoost 模型进行了比较。稍后,您将了解 XGBoost 中对超参数的描述。
以下是 XGBoost 模型中未调整参数的代码示例:
输出:34624.229980
现在让我们看看当参数调整到一定程度时 RMSE 的值:
产量:29812.683594
可以看出,当调整参数时,RMSE 分数降低了大约 15%。
XGBoost 超参数
对于 XGBoost 的每个基础学习者,都有不同的参数可以调整,以提高模型性能。
通用树可调参数
- **学习率:**使用额外的基本学习器会影响模型拟合残差的速度。低学习率将需要更多的提升回合来实现与具有高学习率的 XGBoost 模型相同的残差减少。它用 eta 表示。
- gamma: 这是创建新树分裂的最小损失减少。为了使算法更加保守,最好使用高 gamma 值。
- lambda: 这负责对叶权重进行 L2 正则化。
- alpha: 这负责叶子权重的 L1 正则化。
- max_depth: 它是一个正整数值,决定了每棵树在任何一轮提升中的生长深度。
- **子样本:**范围从 0 到 1,是可用于任何给定增强回合的总训练集的分数。该参数的低值可能导致不匹配问题,而高值可能导致过度匹配。
- colsample_bytree: 该参数的取值范围也是从 0 到 1。它是在任何给定的提升回合期间可以选择的特征的一部分。
线性可调参数
- lambda: 这负责对叶权重进行 L2 正则化。
- **阿尔法:**这负责对叶权重进行 L1 正则化。
- lambda_bias: 可以应用于模型偏差的 L2 正则化项。
以下是一些调整示例:
调谐 ETA
输出:
可以看出,eta 值的增加给出了更好的模型。
调整最大深度
输出:
可以看出,增加树深度的值给出了更好的模型。
只有当超参数的值是最优的时,模型才会给出更好的性能。所以,问题是如何找到最优值以获得尽可能低的损失?
艾米丽·莫特在 Unsplash 上的照片
同时选择几个超参数的两种常用策略是网格搜索和随机搜索。
那么,我们来看看两者如何在 XGBoost 中使用?
网格搜索
这是一种彻底搜索可能的参数值集合的方法。例如,如果您必须调整 3 个超参数,并且每个超参数有 4 个可能的值,那么在该参数空间上的网格搜索将尝试所有 64 个配置,并挑选为所使用的指标提供最佳值的配置(这里我们使用均方根误差)。让我们看一个例子。
输出:最佳参数:{ ‘学习率’:0.1,’ n 个估计量’:200,‘子样本’:0.5}最低 RMSE:2000。10001.686868686107
随机搜索
它与网格搜索有些不同。它为您想要搜索的每个超参数创建了一个超参数值范围。它还设置了继续随机搜索的迭代次数。在每次迭代中,在每个超参数的指定值范围内随机抽取一个值,用这些超参数搜索并训练/测试一个模型。在达到最大迭代次数后,它选择具有最佳分数的最佳超参数。
**输出:**最佳参数:{ ’ learning _ rate ‘:2.000000000000001,’ n_estimators’ : 200,‘子样本’:6.0000000000000000005 }最低 RMSE:2 . 2000005
eta best _ RMSE
0 0.001 195736.406250
1 0.010 179932.192708
2 0.100 79797
结论
现在,您知道了调优的含义以及它如何帮助提升模型。本文讨论了调整超参数 eta 和 max-depth,但是也可以将其他超参数调整到最佳值,从而为您的模型提供更好的性能,并且可以在网格搜索和随机搜索的帮助下选择最佳值。
如果您想了解更多关于超参数调优的一般信息,请考虑以下链接:https://Neptune . ai/blog/hyperparameter-tuning-in-python-a-complete-guide-2020
浓缩咖啡中的粉末不会迁移
咖啡数据科学
一些数据反对拍摄过程中微粒迁移的观点
有一种观点认为,在一杯浓缩咖啡中,更细的颗粒(<200 um), also called fines, migrate to the bottom of the filter and either block the filter (causing channeling) or coming out in the cup (ruining the taste). This concept drives people to be more careful in their puck preparation for fear that too much agitation will cause the fines to accumulate at the bottom and make this problem worse.
)已经做了一些工作来表明搅动会导致减少喷射时间的效果,作者认为这是由干迁移引起的。然而,还没有做很多工作来具体了解冰球中的精细迁移。
我开始使用透明的 portafilter 和射后圆盘分析来研究微粒的迁移。为了了解粉末在水中是如何移动的,我用粉笔在冰球的顶部。如果微粒真的在拍摄过程中迁移,粉笔也会迁移,因为微粒非常细小。然而,我没有观察到任何迁移,并且潜在地发现了缺少迁移背后的原因
以前的工作
苏格拉底咖啡公司做了一个实验,他们筛选咖啡,并观察有两层的照片,以了解底部有更多的细粒是否会导致流动或提取。他们没有发现任何显著的差异,这表明如果微粒确实迁移了,它们不会对拍摄产生大的影响。
咖啡师 Hustle 做了一个实验,通过敲击过滤篮 100 次,专注于圆盘准备过程中的微粒迁移,其概念是较细的微粒应该漂移到底部。然而,他们没有发现它对拍摄时间有影响,这表明要么微粒没有迁移,要么它们的迁移没有引起问题。
在开发和研究了断奏后,我确定粉末不会堵塞过滤篮。断奏法浓缩咖啡,底层是细层(< 400um),镜头依然定时流动。如果细颗粒真的堵塞了过滤孔,那么这个镜头就不可能工作。
精细层还有其他有趣的属性,比如前一两滴流动非常快,但随后该层会凝固。然后,咖啡缓慢流动,随着提取的咖啡越多,流动越快。
透明移动式过滤器
我认为理解这个问题的最好工具是一个透明的过滤篮。虽然已经制造了一些,但是它们通常是偏移的,导致它们在圆盘的顶部和淋浴帘之间具有异常大的顶部空间。通常,这个空间保持在一个镍币的深度,但是在以前制造的透明过滤器中,这个空间是过滤器的厚度。
我用 Kompresso 作为一个透明的便携式过滤器,将咖啡放在淋浴头上方。
然后,我需要一种像咖啡中的粉末一样精细的物质,它不会溶解,但在注射后能够容易地看到。我第一次尝试面粉,但是面粉在热水中会粘在一起。于是我找到一些粉笔,用小刀刮下一些,很快就变成了细粉。我把这个放在咖啡上面。
论点:如果细颗粒迁移,那么细粉笔也应该从顶部迁移到咖啡球中更深的地方。
作者提供的图片与本文中的所有图片一样
首次测试
在测试过程中,我没有观察到粉笔移动到更深的冰球。当水进入试管时,粉笔在水中扬起,即使施加了压力,粉笔似乎也没有受到太大影响。这可能是由于粉末的表面积很小,压力作用很小。
移动水的主要因素是压力,压力导致流动,但流动只能推动相对于表面积的粒子。所以我怀疑在咖啡床移动和不紧密的方法中会发生细微的迁移,但在浓缩咖啡中,这似乎很难证明。
我兴奋地把冰球拉了出来,它碎了。但是我没有看到比顶端更深的粉笔。
第二次测试
所以我用更多的粉笔再次测试。投篮后,当我把冰球拿出来时,我更加注意了。这里的是视频的链接。
视频中的一些图片。我希望看到细颗粒从侧面流下,如果他们考虑到流动通常在中心之前从过滤器或管子的侧面流下。
我只在冰球顶部发现了粉笔。我用多种方法切割冰球,除了粉笔深入的顶部,我没有观察到任何一层。
这是一个横截面,粉笔只出现在顶部。
图像的底部是过滤器的底部
这是另一个更近的观察。
放大
多拍几张照片以防其他的不可信。
左图:显示了底部的底部和靠近过滤器的顶部,在那里过滤器被推出。右:在图像底部显示圆盘的顶部。
这个简单的实验并没有显示出浓缩咖啡的细微迁移的证据。没有发现比顶部更深的测试微粒。一些观察表明,由于表面积较小,当施加更多压力时,细颗粒会旋转,不会比其他颗粒被推得更低。
如果你愿意,可以在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡视频和浓缩咖啡相关的东西。你也可以在 LinkedIn 上找到我。也可以关注我中。
我的进一步阅读:
搅拌还是旋转:更好的浓缩咖啡体验
使用 PyTorch 微调面部识别分类器来识别您的面部
利用 GANs 的对抗性攻击来欺骗面部识别系统。
这是我正在写的一个系列的一部分,关于利用 GANs 的对抗性攻击来欺骗面部识别系统。
然而,在我们欺骗面部识别分类器之前,我们需要建立一个来欺骗。我个人想造一个能识别自己脸的。我可以从一个预先训练好的网络开始,然后微调它来识别我的脸,而不是从头开始训练神经网络。微调是非常有益的,因为我们可以从已经在大规模人脸数据库上训练的模型权重开始,然后更新其中的一些权重,以反映我们希望它执行的新任务。这些权重已经知道如何识别人脸,但唯一的区别是它不知道我的脸。因此,让这个预训练的模型学习我自己的脸要容易得多,因为模型权重已经包含了执行任务所需的大部分信息。
目录结构
project
│ README.md
│ AGN.ipynb
│
└───data
│ │ files_sample.csv
│ └───eyeglasses
│ │
│ └───test_me
│ └───train
| └───Adrien_Brody
| ...
| └───Michael_Chaykowsky
| ...
| └───Venus_Williams
│ └───val
| └───Adrien_Brody
| ...
| └───Michael_Chaykowsky
| ...
| └───Venus_Williams
│
└───models
│ │ inception_resnet_v1.py
│ │ mtcnn.py
│ └───utils
models
目录来自基于上面链接的 Tensorflow 实现的 PyTorch facenet 实现。
└───models
│ │ inception_resnet_v1.py
│ │ mtcnn.py
│ └───utils
这个inception_resnet_v1.py
文件是我们将引入预训练模型的地方。Inception Resnet V1 模型在 VGGFace2 上进行了预训练,其中 VGGFace2 是从谷歌图像搜索中开发的大规模人脸识别数据集,并且“在姿势、年龄、光照、种族和职业方面有很大的变化”
模型中每层的权重都有一个名为requires_grad
的属性,可以设置为True
或False
。当您在训练循环中运行loss.backward()
时,这些权重会更新,这包含了执行预测所需的所有信息。当微调网络时,我们通过将requires_grad
属性设置为False
冻结最后一个卷积块的所有层,然后只更新剩余层上的权重——直观地,您可以想象早期的层包含识别人脸属性和基本层特征所需的基本层信息,因此我们在更新最终层以包括另一张人脸(我的)的同时保持所有性能。
所有的train
目录都有每个人的 11 或 12 个图像,所有的val
目录都有每个人的 4 或 5 个图像。Michael_Chaykowsky
是我的脸部目录,在那里我使用了各种姿势、灯光和角度。为了收集这些图像,我用标准的 iPhone 在不同的空间拍摄了视频,然后将这些视频转换为图像,并在每个视频上使用 MTCNN 进行面部对齐和裁剪到适当的大小(160 x 160 像素)。
进口
**from** torch **import** nn, optim, as_tensor
**from** torch.utils.data **import** Dataset, DataLoader
**import** torch.nn.functional **as** F
**from** torch.optim **import** lr_scheduler
**from** torch.nn.init **import** *
**from** torchvision **import** transforms, utils, datasets, models
**from** models.inception_resnet_v1 **import** InceptionResnetV1**import** cv2
**from** PIL **import** Image
**from** pdb **import** set_trace
**import** time
**import** copy**from** pathlib **import** Path
**import** os
**import** sys
**import** matplotlib.pyplot **as** plt
**import** matplotlib.animation **as** animation
**from** skimage **import** io, transform
**from** tqdm **import** trange, tqdm
**import** csv
**import** glob
**import** dlib**import** pandas **as** pd
**import** numpy **as** np
用于人脸对齐的多任务级联卷积神经网络
**from** IPython.display **import** VideoVideo("data/IMG_2411.MOV", width=200, height=350)
我旋转脸的视频
将视频帧捕获为.png
文件,并旋转/裁剪/对齐。
vidcap = cv2.VideoCapture('IMG_2411.MOV')
success,image = vidcap.read()
count = 0
success = **True**
**while** success:
cv2.imwrite(f"./Michael_Chaykowsky/Michael_Chaykowsky_{
format(count, '04d') }.png", image)
success,image = vidcap.read()
print('Read a new frame: ', success)
count += 1
这些图像是旋转的,所以我用imagemagick
让它们正面朝上。确保先进行brew install imagemagick
。我认为有另一种方法来安装这个库,但如果我回忆起来这是一个噩梦——所以一定要建议brew install
。
%%!
**for** szFile **in** ./Michael_Chaykowsky/*.png
**do**
magick mogrify -rotate 90 ./Michael_Chaykowsky/"$(basename "$szFile")" ;
**done**
! pip install autocrop
Autocrop 有一个很好的功能,他们可以调整人脸图像的大小,并且你可以指定人脸的百分比。如果你使用的是完整的 MTCNN 方法,你可以放弃这个方法(preferred),但如果不是,你可以这样做,这要容易得多。
! autocrop -i ./me_orig/Michael_Chaykowsky -o ./me/Michael_Chaykowsky160 -w 720 -H 720 --facePercent 80
! pip install tensorflow==1.13.0rc1
! pip install scipy==1.1.0
现在使用 facenet 的大卫·桑德伯格 Tensorflow 实现中的align_dataset_mtcnn.py
脚本,我们可以将它应用到人脸图像目录中。
%%!
**for** N **in** {1..4}; **do** \
python ~/Adversarial/data/align/align_dataset_mtcnn.py \ # tensorflow script
~/Adversarial/data/me/ \ # current directory
~/Adversarial/data/me160/ \ # new directory
--image_size 160 \
--margin 32 \
--random_order \
--gpu_memory_fraction 0.25 \
& **done**
现在你已经有了一个目录,所有的面都被对齐并适当地裁剪以用于建模。
加载数据
当我们加载数据时,我们将对图像进行一些随机变换,以改善训练。可以尝试不同的转换,您可以尝试不同的转换,例如
随机颜色抖动
随机旋转+/- 5 度
随机水平翻转
这里我使用随机水平翻转。所有这些变换使模型更具普遍性,并防止过度拟合。
data_transforms = {
'**train**': transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
'**val**': transforms.Compose([
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
}data_dir = 'data/test_me'
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
data_transforms[x])
**for** x **in** ['train', 'val']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x],
batch_size=8,
shuffle=True)
**for** x **in** ['train', 'val']}
dataset_sizes = {x: **len**(image_datasets[x]) **for** x **in** ['train','val']}
class_names = image_datasets['train'].classes
class_names
Out[1]:
['Adrien_Brody','Alejandro_Toledo','Angelina_Jolie','Arnold_Schwarzenegger','Carlos_Moya','Charles_Moose','James_Blake','Jennifer_Lopez','Michael_Chaykowsky','Roh_Moo-hyun','Venus_Williams']
**def** imshow(inp, title=**None**):
"""Imshow for Tensor."""
inp = inp.numpy().transpose((1, 2, 0))
mean = np.array([0.485, 0.456, 0.406])
std = np.array([0.229, 0.224, 0.225])
inp = std * inp + mean
inp = np.clip(inp, 0, 1)
plt.imshow(inp)
**if** title **is** **not** **None**:
plt.title(title)
plt.pause(0.001) # pause a bit so that plots are updated# Get a batch of training data
inputs, classes = **next**(**iter**(dataloaders['train']))# Make a grid from batch
out = utils.make_grid(inputs)imshow(out, title=[class_names[x] **for** x **in** classes])
获取 VGGFace2 数据集上的预训练 ResNet
**from** models.inception_resnet_v1 **import** InceptionResnetV1**print**('Running on device: {}'.format(device))model_ft = InceptionResnetV1(pretrained='vggface2', classify=**False**, num_classes = **len**(class_names))
冻结早期层
回想之前我提到过,我们将冻结穿过最后一个 conv 街区。为了找到它的位置,我们可以使用-n, -n-1, ...
遍历这个列表,直到找到这个块。
list(model_ft.children())[-6:]
Out[2]:
[Block8(
(branch0): BasicConv2d(
(conv): Conv2d(1792, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU()
)
(branch1): Sequential(
(0): BasicConv2d(
(conv): Conv2d(1792, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU()
)
(1): BasicConv2d(
(conv): Conv2d(192, 192, kernel_size=(1, 3), stride=(1, 1), padding=(0, 1), bias=False)
(bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU()
)
(2): BasicConv2d(
(conv): Conv2d(192, 192, kernel_size=(3, 1), stride=(1, 1), padding=(1, 0), bias=False)
(bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU()
)
)
(conv2d): Conv2d(384, 1792, kernel_size=(1, 1), stride=(1, 1))
),
AdaptiveAvgPool2d(output_size=1),
Linear(in_features=1792, out_features=512, bias=False),
BatchNorm1d(512, eps=0.001, momentum=0.1, affine=True, track_running_stats=True),
Linear(in_features=512, out_features=8631, bias=True),
Softmax(dim=1)]
移除 conv 块后的最后几层,放入layer_list
。
layer_list = list(model_ft.children())[-5:] # all final layers
layer_list
Out[3]:
[AdaptiveAvgPool2d(output_size=1),
Linear(in_features=1792, out_features=512, bias=False),
BatchNorm1d(512, eps=0.001, momentum=0.1, affine=True, track_running_stats=True),
Linear(in_features=512, out_features=8631, bias=True),
Softmax(dim=1)]
将所有开始的层放在一个nn.Sequential
中。model_ft
现在是一个火炬模型,但没有最终的线性,汇集,batchnorm 和 sigmoid 层。
model_ft = nn.Sequential(***list**(model_ft.children())[:-5])
如果训练只是最后一层:
**for** param **in** model_ft.parameters():
param.requires_grad = **False**
重新连接自动设置requires_grad = True
的最后 5 层。
这个线性层Linear(in_features=1792, out_features=512, bias=False)
实际上需要编写两个自定义类,这看起来不太明显,但是如果你查看数据输入/输出,你可以看到在这个层中有一个扁平化和正常化类。检查 resnet 实现在last_linear
层整形的原因。
**class** Flatten(nn.Module):
**def** __init__(self):
**super**(Flatten, self).__init__()
**def** forward(self, x):
x = x.view(x.size(0), -1)
**return** x**class** normalize(nn.Module):
**def** __init__(self):
**super**(normalize, self).__init__()
**def** forward(self, x):
x = F.normalize(x, p=2, dim=1)
**return** x
然后,您可以将最后的层应用回新的顺序模型。
model_ft.avgpool_1a = nn.AdaptiveAvgPool2d(output_size=1)
model_ft.last_linear = nn.Sequential(
Flatten(),
nn.Linear(in_features=1792, out_features=512, bias=False),
normalize()
)
model_ft.logits = nn.Linear(layer_list[3].in_features, len(class_names))
model_ft.softmax = nn.Softmax(dim=1)model_ft = model_ft.to(device)criterion = nn.CrossEntropyLoss()# Observe that all parameters are being optimized
optimizer_ft = optim.SGD(model_ft.parameters(), lr=1e-2, momentum=0.9)# Decay LR by a factor of *gamma* every *step_size* epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)
火车
**def** train_model(model, criterion, optimizer, scheduler,
num_epochs=25):
since = time.time()
FT_losses = []
best_model_wts = copy.deepcopy(model.state_dict())
best_acc = 0.0 **for** epoch **in** **range**(num_epochs):
**print**('Epoch {}/{}'.format(epoch, num_epochs - 1))
**print**('-' * 10) # Each epoch has a training and validation phase
**for** phase **in** ['train', 'val']:
**if** phase **==** 'train':
model.train() # Set model to training mode
**else**:
model.eval() # Set model to evaluate mode running_loss = 0.0
running_corrects = 0 # Iterate over data.
**for** inputs, labels **in** dataloaders[phase]:
inputs = inputs.to(device)
labels = labels.to(device) # zero the parameter gradients
optimizer.zero_grad() # forward
# track history if only in train
**with** torch.set_grad_enabled(phase == 'train'):
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
loss = criterion(outputs, labels) # backward + optimize only if in training phase
**if** phase **==** 'train':
loss.backward()
optimizer.step()
scheduler.step()
FT_losses.append(loss.item())
# statistics
running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data) epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects.double() /
dataset_sizes[phase] **print**('{} Loss: {:.4f} Acc: {:.4f}'.format(
phase, epoch_loss, epoch_acc)) # deep copy the model
**if** phase **==** 'val' **and** epoch_acc > best_acc:
best_acc = epoch_acc
best_model_wts = copy.deepcopy(model.state_dict()) time_elapsed = time.time() - since
**print**('Training complete in {:.0f}m {:.0f}s'.format(
time_elapsed // 60, time_elapsed % 60))
**print**('Best val Acc: {:4f}'.format(best_acc)) # load best model weights
model.load_state_dict(best_model_wts)
**return** model, FT_losses
评价
model_ft, FT_losses = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler, num_epochs=500)plt.figure(figsize=(10,5))
plt.title("FRT Loss During Training")
plt.plot(FT_losses, label="FT loss")
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.legend()
plt.show()
还会有更多
请关注本系列的更多内容,我将描述如何使用 GANs 的对抗性攻击来欺骗这个分类器。
火:简单的气候做对了
创建 CLI 有助于提高 ML 管道的可访问性和重用性,但是设置它们可能会很麻烦。进入火场。
照片由来自 Unsplash 的 Cullan Smith 拍摄
什么是火?
几个月前,我大发雷霆,我表达了我对创建进度条的一个叫做TQDM
的小软件包的赞赏。这篇文章也是同样的思路,但这次是关于Fire
:一个伟大的包,它可以让命令行界面(CLI)在几秒钟内(字面上)建立并运行起来轻而易举。
那么,为什么要写 CLI 呢?实际上,一个简单的 CLI 可以使配置脚本变得像更改几个命令行参数一样简单。假设你有一个设置在编排服务上的脚本(可能类似于 Jenkins ),它定期重新训练你最新最棒的 Tweet 情感分类器。假设是一个 Scikit-Learn 随机森林。您可以像这样运行作业:
python tweet-classifier/train.py
您可以选择直接在代码中调整模型的参数。一个更好的方法是将模型封装在一个函数中,并将模型的参数绑定到一个函数签名(例如train(n_estimators: int = 100)
)上,而不是运行类似如下的代码:
python tweet-classifier/train.py --n_estimators 100
这将允许您通过 CLI 配置模型的超参数。
这个防止你为了改变脚本的配置而需要改变代码本身,这反过来可以帮助其他用户轻松地获取和运行你的代码。对于“生产”代码来说,这通常也是一个更好的想法,对源代码的更改应该被仔细跟踪,并传播到该代码的所有实例(您可能正在运行几十个不同的 Tweet 分类器,更改其中一个代码可能会以意想不到的方式改变它们)。几十个陈旧的 Git 分支对此也是一个糟糕的解决方案。对已部署代码的特别更改会造成混乱,最终会让您(或您的雇主)损失一大笔钱。CLIs 可以成为你工具箱里的一个工具,帮助你避免这种命运。
虽然使用 Python 的标准库来设置 CLI 是可能的(并且仍然非常简单),但是它很快就会变得冗长。下面是一个简单的例子,说明如何为一个脚本创建一个 CLI 来打印到n
的斐波那契数列:
# fib.py
import argparsedef fibonacci(n, a=0, b=1):
"""Print the Fibonacci series up to n."""
while a <= n:
print(a)
a, b = b, a + bif __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-n", type=int)
args = parser.parse_args()
fibonacci(args.n)
现在,要执行它,您可以运行:
python fib.py -n 13
您应该看到 Fibonacci 序列打印到控制台。很简单。现在让我们假设你想设置你的初始条件a
和b
。您可以通过以下方式实现:
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-n", type=int)
parser.add_argument("-a", type=int)
parser.add_argument("-b", type=int)
args = parser.parse_args()
fibonacci(args.n, a=args.a, b=args.b)
仍然很简单,但是你的代码很快变得有点臃肿。您可能会想象,随着函数签名中参数数量的增加(或添加新函数),我们的ArgumentParser
块将会快速增长,跟踪所有这些函数及其参数可能会变得非常复杂,最终可能会构成几十行代码。此外,对于简单的脚本来说,这可能是一个足够大的障碍,添加 CLI 可能看起来有点痛苦——也许您可能甚至想直接在脚本中更改一些参数。
输入Fire
这就是Fire
的用武之地:它自动从函数/方法签名生成 CLI。让我们看看最初的例子,但是这次使用Fire
:
# fib.py
import firedef fibonacci(n, a=0, b=1):
"""Print the Fibonacci series up to n."""
while a <= n:
print(a)
a, b = b, a + bif __name__ == "__main__":
fire.Fire(fibonacci)
首先,你可以用pip install fire
把Fire
安装成一个普通的 Python 包。在Fire
的项目报告上有更多的安装细节。您现在可以运行:
python fib.py -n 13
您应该会看到相同的输出。您也可以运行:
python fib.py -n 13 -a 0 -b 1
如您所见,Fire
自动将函数签名中的参数(即函数参数)映射到相应的 CLI 参数。只需一行代码,您就可以启动并运行 CLI。但这只是一个简单的案例。让我们假设您想要为一个脚本创建一个具有许多不同功能的 CLI(可能一个用于训练模型,一个用于运行推理等等。).下面是前面例子的修改版本,引入了一个新的函数和一个新的 tuple 对象struct
:
# cli.py
import firedef fibonacci(n, a=0, b=1):
"""Print the Fibonacci series up to n."""
while a <= n:
print(a)
a, b = b, a + bdef say_hello(name):
print(f"Hello, {name}!")struct = tuple(["a", "b"])if __name__ == "__main__":
fire.Fire()
这做了一些非常有趣的事情。如果将Fire
函数调用的第一个参数留空,那么Fire
会检查当前模块中的 Python 对象,并通过 CLI 公开它们。例如,您可以运行:
python cli.py fibonacci -n 13
这将像以前一样运行fibonacci
功能。但是,您也可以运行:
python cli.py say_hello Jane
这将产生预期的输出Hello, Jane!
。也许最有趣的是,你也可以运行:
python cli.py struct 0
您应该会看到输出a
。同样,您可以运行:
python cli.py struct 1
且看b
。在这种情况下,Fire
让您直接从 CLI 与本地 Python 对象直接交互。您也可以将这个想法扩展到您自己的定制数据结构中。
结束语
Fire
是一个很棒的工具,可以在几秒钟内让干净、高质量的 CLIs 启动并运行,它非常简单,你几乎不需要学习任何新概念就可以在工作中使用它。将一个新模型管道快速包装在一个闪亮的新 CLI 中以备部署是理想的。还有一堆更高级的特性你也可以用来构建更复杂的 CLI。
然而,如果你正在构建一个全功能的 CLI 并想要细粒度的控制,你可能想看看古老而超级灵活的[click](https://github.com/pallets/click)
库。这将为您提供尽可能多的控制(至少在 Python 中)。
启动你的中心性度量引擎:Neo4j vs NetworkX——一场各种各样的拉力赛…
埃洛伊·卡拉斯科在 Unsplash 上拍摄的照片
在本文中,我和 Mark Needham 在 Python NetworkX 包和 Neo4j 图形数据科学库之间进行了一场赛跑。在这场比赛中,我们让他们比赛,看谁在计算无向图的介数和调和中心度方面最快。我们还将使用 Neo4j Python 驱动程序,这样我们甚至不需要离开 Python IDE 的舒适环境。
为什么选择 NetworkX?
NetworkX 包是最老的产品,早在 2008 年就发布了(…那时候 Neo4j 还在早餐喝热巧克力…)。从很多方面来看,它仍然是最流行的网络分析 Python 包,事实上,根据我们的统计,至少有四本关于网络分析的书籍使用 NetworkX 包作为他们的主力。
哪种中心性指标?
如前所述,我们关注两个重要的网络中心性度量:中间性和调和中心性度量。介数度量基于通过它的最短路径的分数来测量节点的重要性,因此它也经常被视为它捕获了多少“信息流”。如下图所示:如果 Alice 离开,许多节点将会孤立。
调和中心性度量是对接近度度量的改进,因此也是“基于距离”的度量,其中高分意味着该特定节点平均来说可以以更少的跳数到达其他节点。正如我们在前一篇文章中所讨论的;根据 Boldi & Vigna 的说法,这是一种“黄金标准”。
我们将本文分为两个主要部分:首先探索 Neo4j 数据库和 Python 驱动程序以及 NetworkX 算法的设置,在第二部分,我们对竞争对手的实际性能进行实验和评论。
为比赛做准备…
关于如何在 Neo4j 中创建数据库以及 Python 驱动程序的一个很棒的帖子是由杨发表的。然而,我们要做的改变是让 Neo4j Python 驱动程序从数据库的“导入”目录中读取文件(与亦舒文章中的 URL 相反)。当我们想要对多个不同的文件执行网络分析时,这是很方便的,我们稍后将回到这一点。最后一步,我们通过“pip install neo4j”在我们的环境中安装 Neo4j Python 驱动程序,或者通过在 PyCharm 中将驱动程序作为一个包加载来更容易地安装。
使用 Neo4j Python 驱动程序构建数据库。
从下面的代码可以看出;只需几行简单的代码就可以访问 Neo4j 丰富的工具和功能。我们首先实例化驱动程序,其中参数是 bolt 连接器,我们可以在 Neo4j 浏览器中找到,然后是用户和密码的验证,这是我们在创建项目时建立的。
接下来,我们定义希望驱动程序执行的 Neo4j 查询,这些查询需要用密码语言编写。两个主要语句中的第一个将在图的节点上创建一个约束,以确保它是唯一的。该约束还将在 Node(id)属性上创建一个索引,这将显著提高运行时间。第二个查询是创建数据库的标准 Cypher 语句,就像我们在 Neo4j 环境中使用的一样,我们还确保文件路径在文件名前引用了一个 file:/// 前缀。
我们现在准备运行驱动程序会话,我们分两步执行。第一次运行使用“创建或替换数据库”语句,该语句针对系统数据库执行,并允许“动态”创建数据库。第二次运行执行约束创建语句和数据库导入语句。作为一项安全检查,我们还将节点算作一个查询。
在 Neo4j 中计算中心性度量
插入数据后,我们可以使用 Neo4j 图形数据库(GDS)计算中心性指标。使用 Cypher 调用 GDS 过程没有什么特别之处,如下面的代码所示,当检索到相应的指标时,我们需要进行一些格式化,以便测量 Neo4j 和 NetworkX 输出之间的准确性。我们通过使用 Kendall Tau 度量,调用“scipy”库来实现这一点,该库要求两个列表在一个“numpy”数组中。为此,我们调用了我们的 Pandas 朋友,在那里我们对结果进行降序排序,然后需要格式化成一个 numpy 数组,以便在我们的准确性测试中使用。
作为安全措施,我们再次将数据保存到 csv 文件中。当然,这并不是绝对必要的,但是因为我们希望从一个脚本中运行所有的东西,其原因将在稍后变得清楚,当事情出错时,它将被证明是有价值的。
在我们进入 NetworkX 引擎之前,先简单介绍一下我们使用的真实网络。从斯坦福的快照数据库中,我们选择了 http://snap.stanford.edu/data/gemsec-Deezer.html的 Deezer RO 设置。这是一个友谊网络,无向,来自音乐流媒体服务 Deezer,由 41,773 个节点和 125,826 条边组成。
准备好我们的 NetworkX 引擎
我们现在可以漫步到 NextworkX pitlane。Deezer RO 网络有一个简单的边列表,我们使用 NetworkX 函数来加载图形:
G = nx.read_edgelist(RW_network, nodetype=int, comments='#',
delimiter=',', create_using=nx.Graph(), encoding='utf-8')
NetworkX centrality 函数同样简单明了:
HC_nx = nx.harmonic_centrality(G)
BC_nx = nx.betweenness_centrality(G)
最后,我们再一次要求我们的熊猫朋友带来一些结构,但是我们也需要使值正常化。这里我们应用与 Neo4j 算法相同的方法,简单地除以节点数— 1。
两个引擎都准备好了,我们可以将它们滚动到起始网格上,在下面的 Python 脚本中,我们对每个函数调用进行了计时:
让游戏开始吧!
当按下绿色的“Run”按钮时,我们看到 NetworkX 首先在几秒钟内构建了图形,而 Neo4j 使用 Python 驱动程序花费了 9.7 秒。然而,对于 NetworkX 来说,这已经是最好的了。对于调和中心性,Neo4j GDS 仅用了 14 秒就返回了 41,773 个中心性值…网络花了 3,997 秒穿过终点线…(即 1 小时 6 分钟)。当计算 Kendall Tau 时,两个列表完全匹配,得分为 1.00。计算介数中心性变得有些尴尬——Neo4j 在 10 秒内返回一点点值。NetworkX 在 15,284 秒内爬完这条线,大约 4 小时 15 分钟。Kendall Tau 的中间值为 0.986,这只是略微偏离,可能是由于一些平局或浮点差异(使用的硬件:macOS,3.1 GHz 双核英特尔酷睿 i5,8GB 内存)。
对速度的需求
Neo4j GDS 算法的神奇之处是双重的。对于谐波中心性,Neo4j GDS 算法利用多源广度优先搜索方法,这是一种允许在单个 CPU 内核上对同一图形运行多个并发 BFS 的算法。运行介数中心性时,NetworkX 算法在单线程上执行。相比之下,Neo4j GDS 平均划分节点空间,然后对每个分区中的每个节点运行 Brandes 算法,因此它应用了多线程方法。
结论
众所周知,NetworkX 是一个相当慢的包,但这个练习表明,对于中型到大型无向图,Neo4j GDS 成为首选引擎。此外,使用我们之前提到的更改 Neo4j 设置的便利技巧,可以完全使用 Neo4j Python 驱动程序来完成图表分析,因此它允许使用简单的 Python 循环对存储在“import”目录中的一组不同图表进行分析。使用 NetworkX 需要几天时间来分析的一组 50-100k 节点的图表,比如说 20 个,现在可以在几个小时内完成。
王高·a·哈格伯格、丹尼尔·a·舒尔特和彼得·j·斯沃特,“使用 NetworkX 探索网络结构、动力学和功能”,载于第七届 Python 科学会议论文集(SciPy2008) ,格尔·瓦洛夸、特拉维斯·沃特和贾罗德·米尔曼(编辑),(美国加利福尼亚州帕萨迪纳),第 11–15 页,2008 年 8 月
然后,Manuel 等人《越多越好:高效多源图遍历》VLDB 基金会会议录8.4(2014):449–460。