- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
“不要重复自己 (Don’t Repeat Yourself)” ,或 DRY ,是广为人知的软件开发原则。该原则出自《程序员修炼之道: 从小工到专家》 (英文名为 The pragmatic programmer),这是代码设计领域迄今为止阅读量最大的一本书。该原则言简意赅,即: 重用而不要重写其他地方已有的逻辑。这可以确保代码保持同步,使其更易于维护且更健壮。该做法使得对公共代码逻辑的任何更改都会统一地影响所有依赖该公共代码逻辑的代码.
乍一看,Hugging Face transformers 库的设计与 DRY 原则背道而驰。注意力机制的代码被复制到不同的模型文件里不下 50 次。有时整个 BERT 模型的代码都会被复制到其他模型文件中。贡献者在添加新模型时,如果新模型用到了现有的某个模型,我们经常强制要求他们把该现有模型的所有代码复制到新模型代码中,连一个小小的逻辑调整也不例外。我们为什么要这么做?是因为我们太懒抑或是因为我们无力承担将所有公共逻辑集中到一个地方所带来的工作量?
不,我们并不懒 —— 不在 transformers 库中使用 DRY 原则是有意之举。我们决定采用一种与 DRY 不同的设计原则,我们称之为 单模型文件 策略 (single model file policy)。 单一模型文件 策略要求,任何模型的所有代码都只应该放在一个文件中,这个文件就是该模型自己的模型文件。如果读者想了解 BERT 如何是进行推理的,他/她只需要阅读 BERT 的 modeling_bert.py 文件即可。通常情况下,我们拒绝任何将不同模型的相同子模块抽象并集中到一个新文件中的尝试。我们不想要一个包含所有可能的注意力机制的 attention_layer.py .
我们为何作出这样的设计呢?我们将原因概括如下
Transformers 积极鼓励来自外部的贡献。贡献一般有错误修复和新模型添加两类。如果有人发现了某个模型文件中的错误,我们希望他/她很容易就能修复它。没有什么比修复了一个 bug 却发现它导致了其他模型上的 100 个 bug 更令人沮丧的了.
因为每个模型代码相互独立,所以对于只了解他/她正在用的那个模型的人来说,修复它会轻松很多。同样,如果只添加一个新的模型文件,添加新的模型代码以及 review 相应的 PR 会更容易。贡献者不必弄清楚如何在不破坏现有模型的情况下向公共的注意力机制代码添加新功能,代码评审者也缺省地知道这个 PR 不会破坏任何一个现有模型.
我们假设 transformers 库的很多用户不仅会阅读文档,而且会查看实际模型代码并有可能对其进行修改。鉴于 transformers 库被 fork 了 1 万多次,我们的 transformers 论文被引用了 1 千多次,这个假设应该是站得住脚的.
因此,最重要的是让第一次阅读 transformers 模型代码的人能够轻松理解并修改它。在单个模型文件中囊括该模型的所有必要逻辑组件有助于提高可读性和可修改性。处于同样的目的,我们也非常关注变量及方法命名的合理性,我们更喜欢表达力强/可读性强的代码,而不盲目追求短代码.
机器学习领域,尤其是神经网络领域的研究发展非常迅速。一年前最先进的模型今天可能已经过时了。我们甚至不知道明年会流行哪一种注意力机制、位置嵌入或架构。因此,我们无法定义适用于所有模型的标准模板.
例如,两年前,人们可能将 BERT 的自注意力层定义为所有 transformer 模型的标准注意力层。从逻辑上讲,“标准”注意力函数可以移到一个集中性的 attention.py 文件中。但是随后出现了在每层中添加相对位置嵌入的注意力层 (如 T5),多种不同形式的分块注意力层 (Reformer,Longformer,BigBird),以及将位置嵌入和词嵌入分离的注意力机制 (DeBERTa) …… 每当发生这类事情时,我们都不得不问自己是否应该调整“标准”注意力函数,还是说向 attention.py 添加一个新的注意力函数更好。但如果要添加新的注意力函数,我们该如何命名呢? attention_with_positional_embd , reformer_attention 还有 deberta_attention ?
给机器学习模型的组件起通用的名字是危险的,因为关于名字意义的解释可能会很快改变或过时。例如,分块注意力指的是 GPTNeo 的分块注意力,还是 Reformer 的分块注意力,抑或是 BigBird 的分块注意力?注意层是自注意层、交叉注意层,还是两者都包含?如果我们最终决定用模型名称来命名注意力层,我们何不直接把这个注意力函数放在相应的模型文件中?
Transformers 库是不同研究团队创建的统一且完善的机器学习模型的集合。每个机器学习模型通常都对应一篇论文及其官方 GitHub 存储库。机器学习模型一旦发布,后面就很少会对其进行调整或更改.
相反,研究团队倾向于发布基于之前模型构建的新模型,而很少对已发布的代码进行重大更改。在决定 transformers 库的设计原则时,这是一个重要的认知。这意味着一旦将模型架构添加到 transformers 中,模型的基本组件就不会再改变。有可能会发现并修复一些错误,有可能会重命名方法或变量,也有可能对模型的输出或输入格式进行微调,但一般不会改动模型的核心组件。因此,对 transformers 中的所有模型进行大的全局性改动的需求大大减少,这使得每个逻辑模块只存在一次这件事情变得不那么重要,因为我们很少改动它.
第二个认知是模型之间 不 存在双向依赖。新发布的模型可能依赖于现存模型,但很明显,现存模型在逻辑上并不依赖于其前面的模型。例如,T5 部分建立在 BERT 之上,因此 T5 的模型代码在逻辑上可能依赖于 BERT 的模型代码,但 BERT 在逻辑上绝不可能依赖于 T5。因此,重构 BERT 的注意力功能以使其满足 T5 的要求这件事在逻辑上不合理 —— 阅读 BERT 的注意力层代码的人不需要对 T5 有任何了解。同样,这也促使我们不要将注意力层等组件集中到所有模型都可以访问的公共模块中.
另一方面,新模型的代码在逻辑上可能对其前面的模型有一定的依赖性。例如,DeBERTa-v2 的代码确实在某种程度上依赖于 DeBERTa 的代码。通过确保 DeBERTa-v2 的模型代码与 DeBERTa 的保持同步,可以显著提高可维护性。理论上来讲,修复 DeBERTa 中的 bug 的同时也应该修复 DeBERTa-v2 中的相同 bug。我们如何在确保新模型与其依赖的模型保持同步的同时维持 单模型文件 策略?
现在,我们解释一下为什么我们在 “重复自己” 之后加上星号$ {}^{\textbf{*}} $。我们不会无脑复制粘贴现有模型的相应代码,即使看上去我们好像就是这么做的。 Transformers 的核心维护者之一 Sylvain Gugger 发现了一种既尊重 单文件策略 又将可维护性成本控制在一定范围内的好机制。该机制,我们暂且称其为 “复制机制” ,允许我们使用 #Copied from <predecessor_model>.<function> 语句标记某些逻辑组件 (如注意力层函数),从而强制被标记的当前代码与 <predecessor_model> 的 <function> 相同。例如, DeBERTa-v2 类 里的这行代码强制整个 DebertaV2Layer 类除了类名前缀 DeBERTav2 之外须与 DebertaLayer 类 相同。如此可以看到,复制机制使模型代码非常容易理解,同时又显著减少了维护成本。如果有人改动了某个模型的某个函数,则我们可以使用一个自动化工具来更正依赖于这个模型的这个函数的所有其他模型的相应代码.
显然,单文件策略也有缺点,我们在这里简单提两个.
Transformers 的一个主要目标是为所有模型的推理和训练提供统一的 API,以便用户可以在不同模型之间快速切换。但是,如果不允许模型文件使用抽象这一设计模式,则确保跨模型的统一 API 会困难得多。我们通过运行 大量 测试 (截至本文撰写时,每天需要运行大约 2 万次测试) 来解决这个问题,以确保模型遵循一致的 API。在这种情况下,单文件策略要求我们在评审新模型和新测例时非常严格.
其次,有很多研究仅针对机器学习模型的单个组件。 例如 ,有研究团队会致力于研究一种适用于所有现有预训练模型的注意力机制的新形式,如 Rethinking Attention with Performers 一文所做的。我们应该如何将此类研究纳入 transformers 库?确实不好弄。我们应该改变所有现有模型吗?这将违背上文中的第 3 点和第 4 点。还是我们应该添加 100 多个新的模型文件,每个文件都以 Performer... 为前缀?这也很荒谬。遗憾的是,对此类情况我们还没有好的解决方案,我们只能选择不将该论文的成果集成到 transformers 中。等这篇论文获得更多关注并有了性能强大的预训练 checkpoint,我们可能会为其中最重要的模型添加一个新的模型文件,例如目前我们已有 modeling_performer_bert.py .
总而言之,在 🤗 Hugging Face,我们坚信 单文件策略 是适合 transformers 的代码设计理念.
你的想法如何?我们很想听听你的意见!如果你有话要说,欢迎到这个 帖子 下留言.
英文原文: https://hf.co/blog/transformers-design-philosophy 。
原文作者: Patrick von Platen 。
译者: Matrix Yao (姚伟峰),英特尔深度学习工程师,工作方向为 transformer-family 模型在各模态数据上的应用及大规模模型的训练推理.
审校/排版: zhongdongy (阿东) 。
最后此篇关于【不要】重复自己*——如何为现代机器学习设计开源库的文章就讲到这里了,如果你想了解更多关于【不要】重复自己*——如何为现代机器学习设计开源库的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
不要 重复自己* 如何为现代机器学习设计开源库 🤗 Transformers 设计理念 “不要重复自己 (Don’t Repeat Yourself)” ,或 DR
我有这种情况。我有 2 个分支,master 和 develop。 在开发分支上我有一些文件,比如说 tools.js .如果我需要更改这个文件,提交它并将其推送到 Github 开发分支。 一切完成
我要上传图片但首先我想查看图像预览,然后当用户单击另一个 asp:button 时,保存图像。 对于预览部分,我使用以下代码: jQuery(document).ready(functi
我是一名编程初学者,现在从 Python 切换到 Clojure。我正在研究一个质数代码,但我没有弄错。我也想练习递归 (defn true-division [n i] (= (/ n i) (
运行 Python 2.7 执行时: $ python client.py get_emails -a "åäö" 我得到: usage: client.py get_emails [-h] [-a
根据我对“告诉-不要-询问”原则的理解,我的其他类不应该能够调用存储在任何其他类中的数据。因此,根据这一原则, setter/getter 是不受欢迎的。为了防止访问数据,它们通常写为: class
我在寻找什么: 我想使用SIMPLE模式最小化的出色功能,同时仅禁用一项特定功能(禁用内联本地功能)。 更新:答案是否定的,根据我的设置是不可能的。 但对于我来说,鉴于我正在使用Grails,有一种解
根据我对“告诉-不要-询问”原则的理解,我的其他类不应该能够调用存储在任何其他类中的数据。因此,根据这一原则, setter/getter 是不受欢迎的。为了防止访问数据,它们通常写为: class
是否可以不 float 具有样式 UITableViewStylePlain 的 UITableView 的节标题? 我正在 build AcaniChat, an open-source versi
当二进制文件、swfs、jar 和 flvs 在本地更改时,我尝试 pull 入更改,git 尝试 merge 它们并报告冲突。 然后,我分支到一个临时分支,提交本地更改的二进制文件,并在 pull
我正在尝试使用 Pex 来测试一些代码。我有一个具有四个具体实现的抽象类。我为四种具体类型中的每一种都创建了工厂方法。我还为抽象类型创建了一个,除了 this nice thread。说明,Pex 不
我正在将 asp.net mvc 3 和 razor 用于一个项目。在某些情况下,我需要从 Controller 序列化一个数组,将其放入 View 数据并将其分配给一个 js 对象。但是当我使用 输
是否可以让一个 webpack 开发服务器配置多个入口点(网站上有多个页面),每个入口点都有不同的配置? 具体来说,我希望将一个条目(页面的 JS 代码)分成 block ,但不要将另一个条目(带有已
我需要使用 hibernate 将 InputStream 或 byte[] (个人资料图像)保存在表中。这里的代码: @Override public void actualizarFotoPerf
我在一个 android 项目中同时拥有 GMS 和 HMS。 GMS 版本有效,但 HMS 不调用 onMapReady 回调。这是代码: private var mMap: HuaweiM
我有一个单元测试文件: module X04PatMatTest where import AssertError import Test.HUnit import X04PatMat ... 和 h
是否可以将 c++ 库包装到 c 中? 我该怎么做? 有现成的工具吗? (需要访问现有的 c++ 库,但只能使用 C) 最佳答案 您可以用 C 编写面向对象的代码,因此如果它是面向对象的 C++ 库,
我有一个 JSP 页面,它接受 SQL 查询,执行它们然后将结果返回到一个表中。一些结果偶尔会在其中包含 HTML 标记,即 - 结果将返回: This is the returned result!
我有一个问题。 我需要帮助。我一直在寻找解决方案大约 5 个小时。不幸的是没有成功。 我的问题是我有几个 Storyboard并且没有使用 Segue 创建。 我希望将选定的 Tableviewcel
当我尝试运行以下代码时: #include void main() { char *a[10] = {"hi", "hello", "how"}; int i = 0, j = 0;
我是一名优秀的程序员,十分优秀!