# 机器学习代码与系统中的设计模式

- 原标题：Design Patterns in Machine Learning Code and Systems
- 类型：博客
- 来源：eugeneyan.com
- 发布时间：2022-06-12
- 首次精选：2026-10-29 23:09
- 分级：值得看
- 学习层级：进阶
- 主题：[coding-agents-engineering](<https://oplearnx.com/md/topics/coding-agents-engineering.md>)（第 35 位）
- 原文链接：[https://eugeneyan.com//writing/design-patterns](<https://eugeneyan.com//writing/design-patterns>)
- 本站页面：[https://oplearnx.com/resources/pk\_b7a28686ffed](<https://oplearnx.com/resources/pk_b7a28686ffed>)

## 摘要

这是一篇博客文章，讨论机器学习代码和系统中常见的设计模式，主要参照《设计模式》一书。文中涵盖库与代码层面的工厂模式等，并以 PyTorch 的 Dataset 子类化（重写 \_\_len\_\_ 和 \_\_getitem\_\_）为例说明自定义数据集的写法。适合从事机器学习工程、想为设计讨论建立共同词汇的人阅读。

## 中文精读

### 1. 引言：设计模式不只是代码结构

- 设计模式不仅是组织代码的方式，还传递了该模式所解决的问题以及代码或组件打算如何被使用。
- 文中列举的模式多来自《设计模式》（Gang of Four）一书，作者在机器学习代码与系统中观察到这些模式的应用。
- 多数开发者对这些模式已有一定熟悉度，掌握基本概念能提供共同词汇，便于讨论设计与实现。

### 2. 库与代码中的设计模式

- 工厂模式（factory pattern）把训练数据等对象与它们的创建方式解耦；创建过程可能复杂（如分布式数据加载器），提供基础工厂可简化对象创建并给出防止出错的约束。基础工厂可用接口或抽象类定义，新工厂通过子类化并给出自己的实现。
- PyTorch 的 Dataset 是工厂模式例子：自定义数据集需子类化并重写 \_\_len\_\_（返回数据集大小）与 \_\_getitem\_\_（按下标取第 i 个样本）。
- Gensim 的 TextCorpus 是另一例，简化文本文件读取供下游语言建模，用户重写 get\_texts\(\) 逐行（或逐文档）读取处理并返回词序列。
- Hugging Face 的 Dataset 无需子类化，把数据加载为 Apache Arrow 以低内存实现快速查找，并提供流式、交错、打乱等方法。
- 适配器模式（adapter pattern）提升接口之间的兼容性，如 CSV、Parquet、JSON 等数据格式，使接口不兼容的对象（如已存储数据）能协作；在数据管道中常用于把不同格式数据读入标准数据对象（如 Dataframe）。Pandas 有近 20 个读取适配器，Spark 有 Parquet、JSON、CSV、Hive、Text 等适配器。
- Apache Arrow 是跨 Pandas、Spark、Parquet、Cassandra 等框架的标准列式格式；Hugging Face Dataset 用 Arrow 作本地缓存系统。

### 3. 库与代码中的设计模式（续）

- 装饰器模式（decorator pattern）让用户在运行时给已有代码添加功能，而不必改动同类其他对象的结构或行为；Python 中 @decorator 作用于 method\(\) 等价于 method = decorator\(method\)。
- 内置例子是 functools 的 lru\_cache\(\)：用字典缓存最近 x 次调用，把输入参数映射到返回结果；文中以缓存计算斐波那契数为例，cache\_info\(\) 显示 hits=28、misses=16。
- Pytest 用装饰器定义 fixture，为测试生成数据并跨测试共享，在运行测试前调用；文中给出加载样本数据、训练好的 DecisionTree 与 RandomForest 的 fixture 示例。
- 作者常用一个 timer 装饰器测量方法调用耗时并连同结果返回，便于在调用不同延迟模型的原型中查看每次调用耗时。
- 策略模式（strategy pattern）让用户改变对象的预期行为或算法：每种策略建一个对象，运行时行为随所用策略对象而变，从而把算法与客户端解耦，提升灵活性与复用性。
- XGBoost 内置多种建树方法（exact、approx、hist、gpu\_hist）与目标函数（平方误差、logistic、pairwise ranking），也允许按接口提供自定义目标函数（如 squared log 误差）。Hugging Face 的 pipeline 便于用不同策略（语言模型）做推理，支持情感分析、翻译、问答等任务。

### 4. 库与代码中的设计模式（续）

- 迭代器模式（iterator pattern）提供遍历对象集合的方式，把遍历算法与数据容器解耦，用户可自定义算法（如在树结构中切换广度优先与深度优先）。PyTorch 的 DataLoader 可设定批大小、是否打乱、工作进程数，甚至提供自定义 collate 函数。
- 管道模式（pipeline pattern）让用户串联一系列变换，变换包括数据清洗、特征工程、降维等，末尾可接估计器（即机器学习模型），管道接收数据、变换后在末端训练模型。作者认为从数据准备、特征工程到模型超参都可视为可调参数，能串联和变换使管道调参更方便，例如思考对连续变量取 np.log 或给文本特征加 trigram 会如何改变指标。
- 使用管道时应注意各变换步骤的输入输出需有标准格式（如 Pandas Dataframe），且每个变换要有 .fit\(\) 与 .transform\(\) 等必需方法。Sklearn 的 Pipeline 接收变换列表加最终估计器，可用 TransformerMixin 创建自定义变换；Spark MLlib 的管道围绕 dataframe、transformer 与 estimator 构建。

### 5. 系统中的设计模式

- 代理模式（proxy pattern）为生产数据库或服务提供替代物，代理可代表该端点执行任务。一个例子是用缓存代理服务搜索查询：搜索查询符合帕累托原则，少数查询占据大部分请求，缓存热门查询（如查询建议、查询分类、检索与排序）的结果可减少实时推理所需算力。服务先查缓存，命中则直接返回，未命中则走实时推理。
- 另一个例子是用反向代理服务模型：横向扩展时用多台机器提供服务，但仍希望只暴露单一地址，反向代理在单一地址接收请求并分发到多台服务器以分摊负载、保证低延迟。
- 文中引用说明负载均衡器与反向代理的区别：负载均衡器指在多个 Web 服务器间分摊入站请求的服务器；反向代理通常功能更多，包括负载均衡、缓存（减少后端负载并直接返回静态内容）、安全（阻止互联网直接访问后端，可混淆后端或主动检查恶意代码）、SSL 加速（作为 SSL 会话终止点，把加密处理从后端卸载）。
- 中介者模式（mediator pattern）提供服务之间的中间人，限制服务直接通信，迫使服务间接通过中介者协作，防止服务显式引用彼此并实现他人接口，从而促进松耦合。
- 若系统中已有中介者，机器学习服务应通过中介者提供给下游应用（如电商网站、交易订单），而非直接对接；中介者通常有标准要求（如请求 schema）与约束（如延迟、限流）。
- 以 Netflix 首页推荐组件为例：不让各组件自行决定如何展示，而是要求每个组件经过一个中介者，由中介者对组件排序，还可做检查（如排除条目少于 x 的组件）或更新（如跨多个组件去重条目），把组件分配到首页位置。

### 6. 结语：模式即意图的传达

- 知道某个东西是工厂、装饰器还是中介者，有助于按设计意图使用它；反过来，在开发库或系统时用对模式，也能帮助用户理解开发者的意图。
- 文末向读者征询在机器学习代码与系统中还见过哪些常见设计模式。

### 7. 参考与引用

- 参考书目包括《设计模式：可复用面向对象软件的基础》（Design Patterns: Elements of Reusable Object-Oriented Software）、《Head First Design Patterns》、Refactor Guru 设计模式站，以及 Mark Saroufim 的 ml-design patterns 仓库。
- 引用格式：Yan, Ziyou（2022 年 6 月），Design Patterns in Machine Learning Code and Systems，eugeneyan.com，并附对应 BibTeX 条目（title、author、journal、year、month、url）。

---
来源：LearnMap · 学径（https://oplearnx.com）
